Skip to content

Commit

Permalink
Metadata: Configure backing field by attribute on skip navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
smitpatel committed Jul 30, 2020
1 parent eaefa43 commit d1139af
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 47 deletions.
92 changes: 50 additions & 42 deletions src/EFCore/Metadata/Conventions/BackingFieldConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,13 @@ public virtual void ProcessPropertyAdded(
IConventionPropertyBuilder propertyBuilder,
IConventionContext<IConventionPropertyBuilder> context)
{
var field = GetFieldToSet(propertyBuilder.Metadata);
if (field != null)
if (ConfigurationSource.Convention.Overrides(propertyBuilder.Metadata.GetFieldInfoConfigurationSource()))
{
propertyBuilder.HasField(field);
var field = GetFieldToSet(propertyBuilder.Metadata);
if (field != null)
{
propertyBuilder.HasField(field);
}
}
}

Expand All @@ -69,11 +72,51 @@ public virtual void ProcessNavigationAdded(
IConventionNavigationBuilder navigationBuilder,
IConventionContext<IConventionNavigationBuilder> context)
{
var navigation = navigationBuilder.Metadata;
var field = GetFieldToSet(navigation);
if (field != null)
if (ConfigurationSource.Convention.Overrides(navigationBuilder.Metadata.GetFieldInfoConfigurationSource()))
{
var field = GetFieldToSet(navigationBuilder.Metadata);
if (field != null)
{
navigationBuilder.HasField(field);
}
}
}

/// <inheritdoc />
public virtual void ProcessSkipNavigationAdded(
IConventionSkipNavigationBuilder skipNavigationBuilder,
IConventionContext<IConventionSkipNavigationBuilder> context)
{
if (ConfigurationSource.Convention.Overrides(skipNavigationBuilder.Metadata.GetFieldInfoConfigurationSource()))
{
navigation.Builder.HasField(field);
var field = GetFieldToSet(skipNavigationBuilder.Metadata);
if (field != null)
{
skipNavigationBuilder.HasField(field);
}
}
}

/// <inheritdoc />
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
foreach (var property in entityType.GetDeclaredProperties())
{
var ambiguousField = property.FindAnnotation(CoreAnnotationNames.AmbiguousField);
if (ambiguousField != null)
{
if (property.GetFieldName() == null)
{
throw new InvalidOperationException((string)ambiguousField.Value);
}

property.RemoveAnnotation(CoreAnnotationNames.AmbiguousField);
}
}
}
}

Expand Down Expand Up @@ -239,40 +282,5 @@ private static int PrefixBinarySearch<T>(KeyValuePair<string, T>[] array, string
right = middle - 1;
}
}

/// <inheritdoc />
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
foreach (var property in entityType.GetDeclaredProperties())
{
var ambiguousField = property.FindAnnotation(CoreAnnotationNames.AmbiguousField);
if (ambiguousField != null)
{
if (property.GetFieldName() == null)
{
throw new InvalidOperationException((string)ambiguousField.Value);
}

property.RemoveAnnotation(CoreAnnotationNames.AmbiguousField);
}
}
}
}

/// <inheritdoc />
public virtual void ProcessSkipNavigationAdded(
IConventionSkipNavigationBuilder skipNavigationBuilder,
IConventionContext<IConventionSkipNavigationBuilder> context)
{
var field = GetFieldToSet(skipNavigationBuilder.Metadata);
if (field != null)
{
skipNavigationBuilder.HasField(field);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public virtual ConventionSet CreateConventionSet()
var timestampAttributeConvention = new TimestampAttributeConvention(Dependencies);
var backingFieldAttributeConvention = new BackingFieldAttributeConvention(Dependencies);

conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention);
conventionSet.PropertyAddedConventions.Add(backingFieldConvention);
conventionSet.PropertyAddedConventions.Add(concurrencyCheckAttributeConvention);
conventionSet.PropertyAddedConventions.Add(databaseGeneratedAttributeConvention);
Expand All @@ -122,7 +123,6 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.PropertyAddedConventions.Add(maxLengthAttributeConvention);
conventionSet.PropertyAddedConventions.Add(stringLengthAttributeConvention);
conventionSet.PropertyAddedConventions.Add(timestampAttributeConvention);
conventionSet.PropertyAddedConventions.Add(backingFieldAttributeConvention);
conventionSet.PropertyAddedConventions.Add(keyAttributeConvention);
conventionSet.PropertyAddedConventions.Add(keyDiscoveryConvention);
conventionSet.PropertyAddedConventions.Add(foreignKeyPropertyDiscoveryConvention);
Expand Down Expand Up @@ -170,8 +170,8 @@ public virtual ConventionSet CreateConventionSet()

var requiredNavigationAttributeConvention = new RequiredNavigationAttributeConvention(Dependencies);
var nonNullableNavigationConvention = new NonNullableNavigationConvention(Dependencies);
conventionSet.NavigationAddedConventions.Add(backingFieldConvention);
conventionSet.NavigationAddedConventions.Add(new NavigationBackingFieldAttributeConvention(Dependencies));
conventionSet.NavigationAddedConventions.Add(backingFieldConvention);
conventionSet.NavigationAddedConventions.Add(requiredNavigationAttributeConvention);
conventionSet.NavigationAddedConventions.Add(nonNullableNavigationConvention);
conventionSet.NavigationAddedConventions.Add(inversePropertyAttributeConvention);
Expand All @@ -180,6 +180,7 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.NavigationAddedConventions.Add(foreignKeyAttributeConvention);

var manyToManyJoinEntityTypeConvention = new ManyToManyJoinEntityTypeConvention(Dependencies);
conventionSet.SkipNavigationAddedConventions.Add(new SkipNavigationBackingFieldAttributeConvention(Dependencies));
conventionSet.SkipNavigationAddedConventions.Add(backingFieldConvention);
conventionSet.SkipNavigationAddedConventions.Add(manyToManyJoinEntityTypeConvention);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public abstract class NavigationAttributeConventionBase<TAttribute> :
IEntityTypeBaseTypeChangedConvention,
IEntityTypeMemberIgnoredConvention,
INavigationAddedConvention,
ISkipNavigationAddedConvention,
IForeignKeyPrincipalEndChangedConvention
where TAttribute : Attribute
{
Expand Down Expand Up @@ -171,6 +172,23 @@ public virtual void ProcessNavigationAdded(
}
}

/// <inheritdoc />
public virtual void ProcessSkipNavigationAdded(
IConventionSkipNavigationBuilder skipNavigationBuilder,
IConventionContext<IConventionSkipNavigationBuilder> context)
{
var skipNavigation = skipNavigationBuilder.Metadata;
var attributes = GetAttributes<TAttribute>(skipNavigation.DeclaringEntityType, skipNavigation);
foreach (var attribute in attributes)
{
ProcessSkipNavigationAdded(skipNavigationBuilder, attribute, context);
if (((IReadableConventionContext)context).ShouldStopProcessing())
{
break;
}
}
}

/// <inheritdoc />
public virtual void ProcessForeignKeyPrincipalEndChanged(
IConventionForeignKeyBuilder relationshipBuilder,
Expand Down Expand Up @@ -234,8 +252,24 @@ private Type FindCandidateNavigationWithAttributePropertyType([NotNull] Property
protected static IEnumerable<TCustomAttribute> GetAttributes<TCustomAttribute>(
[NotNull] IConventionEntityType entityType, [NotNull] IConventionNavigation navigation)
where TCustomAttribute : Attribute
=> GetAttributes<TCustomAttribute>(entityType, navigation.GetIdentifyingMemberInfo());

/// <summary>
/// Returns the attributes applied to the given skip navigation.
/// </summary>
/// <param name="entityType"> The entity type. </param>
/// <param name="skipNavigation"> The skip navigation. </param>
/// <typeparam name="TCustomAttribute"> The attribute type to look for. </typeparam>
/// <returns> The attributes applied to the given skip navigation. </returns>
protected static IEnumerable<TCustomAttribute> GetAttributes<TCustomAttribute>(
[NotNull] IConventionEntityType entityType, [NotNull] IConventionSkipNavigation skipNavigation)
where TCustomAttribute : Attribute
=> GetAttributes<TCustomAttribute>(entityType, skipNavigation.GetIdentifyingMemberInfo());

private static IEnumerable<TCustomAttribute> GetAttributes<TCustomAttribute>(
[NotNull] IConventionEntityType entityType, [NotNull] MemberInfo memberInfo)
where TCustomAttribute : Attribute
{
var memberInfo = navigation.GetIdentifyingMemberInfo();
if (!entityType.HasClrType()
|| memberInfo == null)
{
Expand Down Expand Up @@ -313,6 +347,18 @@ public virtual void ProcessNavigationAdded(
[NotNull] IConventionContext<IConventionNavigationBuilder> context)
=> throw new NotImplementedException();

/// <summary>
/// Called after a skip navigation property that has an attribute is added to an entity type.
/// </summary>
/// <param name="skipNavigationBuilder"> The builder for the navigation. </param>
/// <param name="attribute"> The attribute. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
public virtual void ProcessSkipNavigationAdded(
[NotNull] IConventionSkipNavigationBuilder skipNavigationBuilder,
[NotNull] TAttribute attribute,
[NotNull] IConventionContext<IConventionSkipNavigationBuilder> context)
=> throw new NotImplementedException();

/// <summary>
/// Called after a navigation property that has an attribute is ignored.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// A convention that configures a skip navigation property as having a backing field
/// based on the <see cref="BackingFieldAttribute"/> attribute.
/// </summary>
public class SkipNavigationBackingFieldAttributeConvention : NavigationAttributeConventionBase<BackingFieldAttribute>
{
/// <summary>
/// Creates a new instance of <see cref="SkipNavigationBackingFieldAttributeConvention" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this convention. </param>
public SkipNavigationBackingFieldAttributeConvention([NotNull] ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

/// <inheritdoc />
public override void ProcessSkipNavigationAdded(
IConventionSkipNavigationBuilder skipNavigationBuilder,
BackingFieldAttribute attribute,
IConventionContext<IConventionSkipNavigationBuilder> context)
{
skipNavigationBuilder.HasField(attribute.Name, fromDataAnnotation: true);
}
}
}
20 changes: 18 additions & 2 deletions test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,22 @@ public virtual void Navigation_properties_can_set_access_mode_using_expressions(
Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode());
}

[ConditionalFact]
public virtual void Skip_navigation_field_can_be_set_via_attribute()
{
var modelBuilder = CreateModelBuilder();

modelBuilder.Ignore<OneToManyNavPrincipal>();
modelBuilder.Ignore<OneToOneNavPrincipal>();
modelBuilder.Entity<ManyToManyNavPrincipal>()
.HasMany(e => e.Dependents)
.WithMany(e => e.ManyToManyPrincipals);

var model = modelBuilder.FinalizeModel();

Assert.Equal("_randomField", model.FindEntityType(typeof(ManyToManyNavPrincipal)).FindSkipNavigation("Dependents").GetFieldName());
}

[ConditionalFact]
public virtual void Navigation_properties_can_set_access_mode_using_navigation_names()
{
Expand All @@ -353,8 +369,8 @@ public virtual void Navigation_properties_can_set_access_mode_using_navigation_n

var model = modelBuilder.FinalizeModel();

var principal = (IEntityType)model.FindEntityType(typeof(ManyToManyNavPrincipal));
var dependent = (IEntityType)model.FindEntityType(typeof(NavDependent));
var principal = model.FindEntityType(typeof(ManyToManyNavPrincipal));
var dependent = model.FindEntityType(typeof(NavDependent));

Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode());
Expand Down
8 changes: 8 additions & 0 deletions test/EFCore.Tests/ModelBuilding/TestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -881,9 +881,17 @@ private class OneToOneNavPrincipal

private class ManyToManyNavPrincipal
{
private readonly List<NavDependent> _randomField;

public ManyToManyNavPrincipal()
{
_randomField = new List<NavDependent>();
}

public int Id { get; set; }
public string Name { get; set; }

[BackingField("_randomField")]
public List<NavDependent> Dependents { get; set; }
}

Expand Down

0 comments on commit d1139af

Please sign in to comment.