Skip to content

Commit

Permalink
Task #1778 : Containment support to webapi.odata
Browse files Browse the repository at this point in the history
1. Able to build an Edm model has a contained navigation property, using
convention model builder
  Add a [Contained] attribute to mark a navigation property as containment.
2. Able to build a model has a contained navigation property, using model
builder
  EntityTypeConfiguration<T>: ContainsMany, ContainsRequired,
ContainsOptional
  These, and existing HasMany etc. can be used to override convention model
builder.
3. Able to build a model with a base type has a contained navigation
property, and the navigation property is inherited by a derived type,
using model builder and convention model builder.
4. Able to route for fundamental scenarios
  ../MyOrders(orderId)/OrdersLines
  ../MyOrders(orderId)/OrdersLines(lineId)
5. Able to serialize / des-serialize containment
6. Containment in expand
7. Calculate links via ODL
  In derived scenario, when generate ID link (should be a Uri as per spec,
instead of string,) we should not append type cast at the end.
  Previously, when webapi.odata generates an Id link, it will check if a
derived type has a nav prop, if yes, it will append a type cast (even if
the actual type is the same to the request one.)
  After we leverage ODL for edit link generation, the edit link will be:
./service_root/es(key)/cast/cast
  As per v4 spec, the editlink/readlink does include the type-cast; however,
according to convention the Id does NOT include the type-cast:
8. Fix test cases about editLink
9. Verify containment after composable function / action
  • Loading branch information
congysu committed Apr 18, 2014
1 parent 11e4c56 commit e4a0d36
Show file tree
Hide file tree
Showing 59 changed files with 1,306 additions and 161 deletions.
12 changes: 12 additions & 0 deletions src/System.Web.OData/OData/Builder/ContainedAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Web.OData.Builder
{
/// <summary>
/// Mark a navigation property as containment.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ContainedAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Apply(INavigationSourceConfiguration configuration, ODataModelBuilde
{
entitySet.HasFeedSelfLink(feedContext =>
{
string selfLink = feedContext.Url.CreateODataLink(new EntitySetPathSegment(feedContext.EntitySet));
string selfLink = feedContext.Url.CreateODataLink(new EntitySetPathSegment(feedContext.EntitySetBase));

if (selfLink == null)
{
Expand All @@ -32,19 +32,29 @@ public void Apply(INavigationSourceConfiguration configuration, ODataModelBuilde
});
}

// We only need to configure the IdLink by convention, ReadLink and EditLink both delegate to IdLink
if (configuration.GetIdLink() == null)
{
bool derivedTypesDefineNavigationProperty = model.DerivedTypes(configuration.EntityType).Any(e => e.NavigationProperties.Any());
configuration.HasIdLink(new SelfLinkBuilder<Uri>((entityContext) => entityContext.GenerateSelfLink(includeCast: false), followsConventions: true));
}

if (configuration.GetEditLink() == null)
{
bool derivedTypesDefineNavigationProperty = model.DerivedTypes(configuration.EntityType).Any(e => e.NavigationProperties.Any());

// generate links with cast if any of the derived types define a navigation property
if (derivedTypesDefineNavigationProperty)
{
configuration.HasIdLink(new SelfLinkBuilder<string>((entityContext) => entityContext.GenerateSelfLink(includeCast: true), followsConventions: true));
configuration.HasEditLink(
new SelfLinkBuilder<Uri>(
entityContext => entityContext.GenerateSelfLink(includeCast: true),
followsConventions: true));
}
else
{
configuration.HasIdLink(new SelfLinkBuilder<string>((entityContext) => entityContext.GenerateSelfLink(includeCast: false), followsConventions: true));
configuration.HasEditLink(
new SelfLinkBuilder<Uri>(
entityContext => entityContext.GenerateSelfLink(includeCast: false),
followsConventions: true));
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/System.Web.OData/OData/Builder/EdmModelHelperMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,19 @@ private static void AddNavigationBindings(NavigationSourceConfiguration configur
foreach (NavigationPropertyConfiguration navigationProperty in entityType.NavigationProperties)
{
NavigationPropertyBindingConfiguration binding = configuration.FindBinding(navigationProperty);
if (binding != null)
bool isContained = navigationProperty.ContainsTarget;
if (binding != null || isContained)
{
EdmEntityType edmEntityType = edmTypeMap[entityType.ClrType] as EdmEntityType;
IEdmNavigationProperty edmNavigationProperty = edmEntityType.NavigationProperties()
.Single(np => np.Name == navigationProperty.Name);

navigationSource.AddNavigationTarget(edmNavigationProperty, edmNavigationSourceMap[binding.TargetNavigationSource.Name]);
if (!isContained)
{
navigationSource.AddNavigationTarget(
edmNavigationProperty,
edmNavigationSourceMap[binding.TargetNavigationSource.Name]);
}

NavigationLinkBuilder linkBuilderFunc = configuration.GetNavigationPropertyLink(navigationProperty);
if (linkBuilderFunc != null)
Expand Down
11 changes: 7 additions & 4 deletions src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,13 @@ private void CreateEntityTypeBody(EdmEntityType type, EntityTypeConfiguration co

foreach (NavigationPropertyConfiguration navProp in config.NavigationProperties)
{
EdmNavigationPropertyInfo info = new EdmNavigationPropertyInfo();
info.Name = navProp.Name;
info.TargetMultiplicity = navProp.Multiplicity;
info.Target = GetEdmType(navProp.RelatedClrType) as IEdmEntityType;
EdmNavigationPropertyInfo info = new EdmNavigationPropertyInfo
{
Name = navProp.Name,
TargetMultiplicity = navProp.Multiplicity,
Target = GetEdmType(navProp.RelatedClrType) as IEdmEntityType,
ContainsTarget = navProp.ContainsTarget
};
IEdmProperty edmProperty = type.AddUnidirectionalNavigation(info);
if (navProp.PropertyInfo != null && edmProperty != null)
{
Expand Down
28 changes: 26 additions & 2 deletions src/System.Web.OData/OData/Builder/EntityTypeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,28 @@ public override CollectionPropertyConfiguration AddCollectionProperty(PropertyIn
}

/// <summary>
/// Adds a new EDM navigation property to this entity type.
/// Adds a non-contained EDM navigation property to this entity type.
/// </summary>
/// <param name="navigationProperty">The backing CLR property.</param>
/// <param name="multiplicity">The <see cref="EdmMultiplicity"/> of the navigation property.</param>
/// <returns>Returns the <see cref="NavigationPropertyConfiguration"/> of the added property.</returns>
public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity)
{
return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: false);
}

/// <summary>
/// Adds a contained EDM navigation property to this entity type.
/// </summary>
/// <param name="navigationProperty">The backing CLR property.</param>
/// <param name="multiplicity">The <see cref="EdmMultiplicity"/> of the navigation property.</param>
/// <returns>Returns the <see cref="NavigationPropertyConfiguration"/> of the added property.</returns>
public virtual NavigationPropertyConfiguration AddContainedNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity)
{
return AddNavigationProperty(navigationProperty, multiplicity, containsTarget: true);
}

private NavigationPropertyConfiguration AddNavigationProperty(PropertyInfo navigationProperty, EdmMultiplicity multiplicity, bool containsTarget)
{
if (navigationProperty == null)
{
Expand Down Expand Up @@ -294,7 +310,15 @@ public virtual NavigationPropertyConfiguration AddNavigationProperty(PropertyInf
}
else
{
navigationPropertyConfig = new NavigationPropertyConfiguration(navigationProperty, multiplicity, this);
navigationPropertyConfig = new NavigationPropertyConfiguration(
navigationProperty,
multiplicity,
this);
if (containsTarget)
{
navigationPropertyConfig = navigationPropertyConfig.Contained();
}

ExplicitProperties[navigationProperty] = navigationPropertyConfig;
// make sure the related type is configured
ModelBuilder.AddEntityType(navigationPropertyConfig.RelatedClrType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,61 @@ public NavigationPropertyConfiguration HasRequired<TTargetEntity>(Expression<Fun
return GetOrCreateNavigationProperty(navigationPropertyExpression, EdmMultiplicity.One);
}

/// <summary>
/// Configures a relationship from this entity type to a contained collection navigation property.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for
/// the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET
/// <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration ContainsMany<TTargetEntity>(
Expression<Func<TEntityType, IEnumerable<TTargetEntity>>> navigationPropertyExpression)
where TTargetEntity : class
{
return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.Many);
}

/// <summary>
/// Configures an optional relationship from this entity type to a single contained navigation property.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for
/// the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET
/// <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration ContainsOptional<TTargetEntity>(
Expression<Func<TEntityType, TTargetEntity>> navigationPropertyExpression) where TTargetEntity : class
{
return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.ZeroOrOne);
}

/// <summary>
/// Configures a required relationship from this entity type to a single contained navigation property.
/// </summary>
/// <typeparam name="TTargetEntity">The type of the entity at the other end of the relationship.</typeparam>
/// <param name="navigationPropertyExpression">A lambda expression representing the navigation property for
/// the relationship. For example, in C# <c>t => t.MyProperty</c> and in Visual Basic .NET
/// <c>Function(t) t.MyProperty</c>.</param>
/// <returns>A configuration object that can be used to further configure the relationship.</returns>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "Nested generic appropriate here")]
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
Justification = "Explicit Expression generic type is more clear")]
public NavigationPropertyConfiguration ContainsRequired<TTargetEntity>(
Expression<Func<TEntityType, TTargetEntity>> navigationPropertyExpression) where TTargetEntity : class
{
return GetOrCreateContainedNavigationProperty(navigationPropertyExpression, EdmMultiplicity.One);
}

/// <summary>
/// Create an Action that binds to this EntityType.
/// </summary>
Expand Down Expand Up @@ -219,5 +274,11 @@ internal NavigationPropertyConfiguration GetOrCreateNavigationProperty(Expressio
PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression);
return _configuration.AddNavigationProperty(navigationProperty, multiplicity);
}

internal NavigationPropertyConfiguration GetOrCreateContainedNavigationProperty(Expression navigationPropertyExpression, EdmMultiplicity multiplicity)
{
PropertyInfo navigationProperty = PropertySelectorVisitor.GetSelectedProperty(navigationPropertyExpression);
return _configuration.AddContainedNavigationProperty(navigationProperty, multiplicity);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public interface INavigationSourceConfiguration
/// </summary>
/// <param name="idLinkBuilder">The builder used to generate the ID.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
INavigationSourceConfiguration HasIdLink(SelfLinkBuilder<string> idLinkBuilder);
INavigationSourceConfiguration HasIdLink(SelfLinkBuilder<Uri> idLinkBuilder);

/// <summary>
/// Configures the navigation link for the given navigation property for this navigation source.
Expand Down Expand Up @@ -145,7 +145,7 @@ NavigationPropertyBindingConfiguration FindBinding(NavigationPropertyConfigurati
/// <returns>The builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "Consistent with EF Has/Get pattern")]
SelfLinkBuilder<string> GetIdLink();
SelfLinkBuilder<Uri> GetIdLink();

/// <summary>
/// Gets the builder used to generate navigation link for the given navigation property for this navigation source.
Expand Down
18 changes: 13 additions & 5 deletions src/System.Web.OData/OData/Builder/LinkGenerationHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Http;
using System.Web.OData.Builder.Conventions;
Expand All @@ -24,7 +25,7 @@ public static class LinkGenerationHelpers
/// <param name="entityContext">The <see cref="EntityInstanceContext"/> representing the entity for which the self link needs to be generated.</param>
/// <param name="includeCast">Represents whether the generated link should have a cast segment representing a type cast.</param>
/// <returns>The self link following the OData URL conventions.</returns>
public static string GenerateSelfLink(this EntityInstanceContext entityContext, bool includeCast)
public static Uri GenerateSelfLink(this EntityInstanceContext entityContext, bool includeCast)
{
if (entityContext == null)
{
Expand All @@ -37,7 +38,8 @@ public static string GenerateSelfLink(this EntityInstanceContext entityContext,

IList<ODataPathSegment> idLinkPathSegments = entityContext.GenerateBaseODataPathSegments();

if (includeCast)
bool isSameType = entityContext.EntityType == entityContext.NavigationSource.EntityType();
if (includeCast && !isSameType)
{
idLinkPathSegments.Add(new CastPathSegment(entityContext.EntityType));
}
Expand All @@ -48,7 +50,7 @@ public static string GenerateSelfLink(this EntityInstanceContext entityContext,
return null;
}

return idLink;
return new Uri(idLink);
}

/// <summary>
Expand Down Expand Up @@ -100,7 +102,7 @@ public static Uri GenerateFeedSelfLink(this FeedContext feedContext)
throw Error.ArgumentNull("feedContext");
}

string selfLink = feedContext.Url.CreateODataLink(new EntitySetPathSegment(feedContext.EntitySet));
string selfLink = feedContext.Url.CreateODataLink(new EntitySetPathSegment(feedContext.EntitySetBase));

return selfLink == null ? null : new Uri(selfLink);
}
Expand Down Expand Up @@ -134,6 +136,12 @@ public static Uri GenerateActionLink(this EntityInstanceContext entityContext, I

internal static Uri GenerateActionLink(this EntityInstanceContext entityContext, string bindingParameterType, string actionName)
{
Contract.Assert(entityContext != null);
if (entityContext.NavigationSource is IEdmContainedEntitySet)
{
return null;
}

IList<ODataPathSegment> actionPathSegments = entityContext.GenerateBaseODataPathSegments();

// generate link with cast if the navigation source doesn't match the entity type the action is bound to.
Expand Down Expand Up @@ -207,7 +215,7 @@ internal static IList<ODataPathSegment> GenerateBaseODataPathSegments(this Entit
}
else
{
odataPath.Add(new EntitySetPathSegment((IEdmEntitySet)entityContext.NavigationSource));
odataPath.Add(new EntitySetPathSegment((IEdmEntitySetBase)entityContext.NavigationSource));
odataPath.Add(new KeyValuePathSegment(ConventionsHelpers.GetEntityKeyValue(entityContext)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public EntityTypeConfiguration DeclaringEntityType
/// </summary>
public EdmMultiplicity Multiplicity { get; private set; }

/// <summary>
/// Gets whether this navigation property is a containment, default to false.
/// </summary>
public bool ContainsTarget { get; private set; }

/// <summary>
/// Gets the backing CLR type of this property type.
/// </summary>
Expand Down Expand Up @@ -103,5 +108,23 @@ public NavigationPropertyConfiguration Required()
Multiplicity = EdmMultiplicity.One;
return this;
}

/// <summary>
/// Marks the navigation property as containment.
/// </summary>
public NavigationPropertyConfiguration Contained()
{
ContainsTarget = true;
return this;
}

/// <summary>
/// Marks the navigation property as non-contained.
/// </summary>
public NavigationPropertyConfiguration NonContained()
{
ContainsTarget = false;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public abstract class NavigationSourceConfiguration : INavigationSourceConfigura

private SelfLinkBuilder<Uri> _editLinkBuilder;
private SelfLinkBuilder<Uri> _readLinkBuilder;
private SelfLinkBuilder<string> _idLinkBuilder;
private SelfLinkBuilder<Uri> _idLinkBuilder;

private readonly Dictionary<NavigationPropertyConfiguration, NavigationPropertyBindingConfiguration>
_navigationPropertyBindings;
Expand Down Expand Up @@ -157,7 +157,7 @@ public virtual INavigationSourceConfiguration HasReadLink(SelfLinkBuilder<Uri> r
/// </summary>
/// <param name="idLinkBuilder">The builder used to generate the ID.</param>
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual INavigationSourceConfiguration HasIdLink(SelfLinkBuilder<string> idLinkBuilder)
public virtual INavigationSourceConfiguration HasIdLink(SelfLinkBuilder<Uri> idLinkBuilder)
{
if (idLinkBuilder == null)
{
Expand Down Expand Up @@ -390,7 +390,7 @@ public virtual SelfLinkBuilder<Uri> GetReadLink()
/// <returns>The builder.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "Consistent with EF Has/Get pattern")]
public virtual SelfLinkBuilder<string> GetIdLink()
public virtual SelfLinkBuilder<Uri> GetIdLink()
{
return _idLinkBuilder;
}
Expand Down
Loading

0 comments on commit e4a0d36

Please sign in to comment.