Skip to content

Commit

Permalink
Partial fix for 6674. Updated to use Navigation() calls directly on E…
Browse files Browse the repository at this point in the history
…ntityBuilder. Added OnetoMany, ManyToOne, ManyToMany, OneToOne and principal side of the Owned Type tests.
  • Loading branch information
lajones committed Mar 13, 2020
1 parent 6f6c1e9 commit caa5582
Show file tree
Hide file tree
Showing 15 changed files with 534 additions and 15 deletions.
13 changes: 13 additions & 0 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,19 @@ public virtual PropertyBuilder IndexedProperty([NotNull] Type propertyType, [Not
Check.NotNull(propertyType, nameof(propertyType)),
Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit).Metadata);

/// <summary>
/// <para>
/// Returns an object that can be used to configure an existing navigation property of the entity type.
/// It is an error for the navigation property not to exist.
/// </para>
/// </summary>
/// <param name="navigationName"> The name of the navigation property to be configured. </param>
/// <returns> An object that can be used to configure the navigation property. </returns>
public virtual NavigationBuilder Navigation([NotNull] string navigationName)
=> new NavigationBuilder(
Builder.Navigation(
Check.NotEmpty(navigationName, nameof(navigationName))));

/// <summary>
/// Excludes the given property from the entity type. This method is typically used to remove properties
/// and navigations from the entity type that were added by convention.
Expand Down
14 changes: 14 additions & 0 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ public virtual PropertyBuilder<TProperty> Property<TProperty>([NotNull] Expressi
Check.NotNull(propertyExpression, nameof(propertyExpression)).GetPropertyAccess(), ConfigurationSource.Explicit)
.Metadata);

/// <summary>
/// Returns an object that can be used to configure an existing navigation property of the entity type.
/// It is an error for the navigation property not to exist.
/// </summary>
/// <param name="navigationExpression">
/// A lambda expression representing the navigation property to be configured (
/// <c>blog => blog.Posts</c>).
/// </param>
/// <returns> An object that can be used to configure the navigation property. </returns>
public virtual NavigationBuilder Navigation<TNavigation>([NotNull] Expression<Func<TEntity, TNavigation>> navigationExpression)
=> new NavigationBuilder(
Builder.Navigation(
Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess()));

/// <summary>
/// Excludes the given property from the entity type. This method is typically used to remove properties
/// or navigations from the entity type that were added by convention.
Expand Down
46 changes: 33 additions & 13 deletions src/EFCore/Metadata/Builders/NavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
/// <summary>
/// <para>
/// Provides a simple API for configuring a <see cref="INavigation" />.
/// Provides a simple API for configuring a <see cref="INavigation" /> or <see cref="ISkipNavigation" />.
/// </para>
/// <para>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </para>
/// </summary>
public class NavigationBuilder : IInfrastructure<IConventionNavigationBuilder>
public class NavigationBuilder : IInfrastructure<IConventionSkipNavigationBuilder>, IInfrastructure<IConventionNavigationBuilder>
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -27,22 +27,25 @@ public class NavigationBuilder : IInfrastructure<IConventionNavigationBuilder>
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public NavigationBuilder([NotNull] IMutableNavigation navigation)
public NavigationBuilder([NotNull] IMutableNavigationBase navigationOrSkipNavigation)
{
Check.NotNull(navigation, nameof(navigation));
Check.NotNull(navigationOrSkipNavigation, nameof(navigationOrSkipNavigation));

Builder = ((Navigation)navigation).Builder;
NavBuilder = (navigationOrSkipNavigation as Navigation)?.Builder;
SkipNavBuilder = (navigationOrSkipNavigation as SkipNavigation)?.Builder;
Metadata = (IMutableNavigationBase)NavBuilder?.Metadata ?? SkipNavBuilder?.Metadata;

Check.DebugAssert(NavBuilder != null || SkipNavBuilder != null, "Expected either a Navigation or SkipNavigation");
}

/// <summary>
/// The internal builder being used to configure the property.
/// </summary>
IConventionNavigationBuilder IInfrastructure<IConventionNavigationBuilder>.Instance => Builder;
private InternalNavigationBuilder NavBuilder { get; }

private InternalSkipNavigationBuilder SkipNavBuilder { get; }

/// <summary>
/// The navigation being configured.
/// </summary>
public virtual IMutableNavigation Metadata => Builder.Metadata;
public virtual IMutableNavigationBase Metadata { get; private set; }

/// <summary>
/// Adds or updates an annotation on the navigation property. If an annotation
Expand All @@ -57,7 +60,14 @@ public virtual NavigationBuilder HasAnnotation([NotNull] string annotation, [Not
Check.NotEmpty(annotation, nameof(annotation));
Check.NotNull(value, nameof(value));

Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
if (NavBuilder != null)
{
NavBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
}
else
{
SkipNavBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit);
}

return this;
}
Expand All @@ -81,12 +91,22 @@ public virtual NavigationBuilder HasAnnotation([NotNull] string annotation, [Not
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual NavigationBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode)
{
Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit);
if (NavBuilder != null)
{
NavBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit);
}
else
{
SkipNavBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit);
}

return this;
}

private InternalNavigationBuilder Builder { get; }
IConventionSkipNavigationBuilder IInfrastructure<IConventionSkipNavigationBuilder>.Instance => SkipNavBuilder;

IConventionNavigationBuilder IInfrastructure<IConventionNavigationBuilder>.Instance => NavBuilder;


#region Hidden System.Object members

Expand Down
29 changes: 29 additions & 0 deletions src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,35 @@ private bool CanRemoveProperty(
return currentConfigurationSource;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IMutableNavigationBase Navigation([NotNull] MemberInfo memberInfo)
=> Navigation(memberInfo.GetSimpleMemberName());

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IMutableNavigationBase Navigation([NotNull] string navigationName)
{
var existingNavigation = Metadata.FindNavigation(navigationName);
var existingSkipNavigation = Metadata.FindSkipNavigation(navigationName);
if (existingNavigation == null
&& existingSkipNavigation == null)
{
throw new InvalidOperationException(
CoreStrings.CanOnlyConfigureExistingNavigations(navigationName, Metadata.DisplayName()));
}

return ((IMutableNavigationBase)existingNavigation) ?? existingSkipNavigation;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1357,4 +1357,7 @@
<data name="AttemptToCreateEntityTypeBasedOnProxyClass" xml:space="preserve">
<value>Cannot add an entity type with type '{typeName}'. That type is a dynamically-generated proxy type.</value>
</data>
<data name="CanOnlyConfigureExistingNavigations" xml:space="preserve">
<value>There is no navigation property with name '{navigationName}' on entity type '{entityType}'. Please add the navigation to the model before configuring it.</value>
</data>
</root>
51 changes: 51 additions & 0 deletions test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Xunit;

// ReSharper disable InconsistentNaming
Expand Down Expand Up @@ -158,6 +159,56 @@ public virtual void Throws_for_conflicting_many_to_one_on_right()
() => modelBuilder.Entity<Product>()
.HasMany(o => o.Categories).WithMany(c => c.Products)).Message);
}

[ConditionalFact]
public virtual void Navigation_properties_can_set_access_mode_using_expressions()
{
var modelBuilder = CreateModelBuilder();
var model = modelBuilder.Model;

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

modelBuilder.Entity<ManyToManyNavPrincipal>()
.Navigation(e => e.Dependents)
.UsePropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<NavDependent>()
.Navigation(e => e.ManyToManyPrincipals)
.UsePropertyAccessMode(PropertyAccessMode.Property);

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

Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode());
}

[ConditionalFact]
public virtual void Navigation_properties_can_set_access_mode_using_navigation_names()
{
var modelBuilder = CreateModelBuilder();
var model = modelBuilder.Model;

modelBuilder.Entity<ManyToManyNavPrincipal>()
.HasMany<NavDependent>("Dependents")
.WithMany("ManyToManyPrincipals");

modelBuilder.Entity<ManyToManyNavPrincipal>()
.Navigation("Dependents")
.UsePropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<NavDependent>()
.Navigation("ManyToManyPrincipals")
.UsePropertyAccessMode(PropertyAccessMode.Property);

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

Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode());
}
}
}
}
50 changes: 50 additions & 0 deletions test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,56 @@ public virtual void Navigation_properties_can_set_access_mode_using_named_HasMan
Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToManyPrincipal").GetPropertyAccessMode());
}

[ConditionalFact]
public virtual void Navigation_properties_can_set_access_mode_using_expressions()
{
var modelBuilder = CreateModelBuilder();
var model = modelBuilder.Model;

modelBuilder.Entity<OneToManyNavPrincipal>()
.HasMany(e => e.Dependents)
.WithOne(e => e.OneToManyPrincipal);

modelBuilder.Entity<OneToManyNavPrincipal>()
.Navigation(e => e.Dependents)
.UsePropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<NavDependent>()
.Navigation(e => e.OneToManyPrincipal)
.UsePropertyAccessMode(PropertyAccessMode.Property);

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

Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToManyPrincipal").GetPropertyAccessMode());
}

[ConditionalFact]
public virtual void Navigation_properties_can_set_access_mode_using_navigation_names()
{
var modelBuilder = CreateModelBuilder();
var model = modelBuilder.Model;

modelBuilder.Entity<OneToManyNavPrincipal>()
.HasMany<NavDependent>("Dependents")
.WithOne("OneToManyPrincipal");

modelBuilder.Entity<OneToManyNavPrincipal>()
.Navigation("Dependents")
.UsePropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<NavDependent>()
.Navigation("OneToManyPrincipal")
.UsePropertyAccessMode(PropertyAccessMode.Property);

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

Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToManyPrincipal").GetPropertyAccessMode());
}

[ConditionalFact]
public virtual void Access_mode_can_be_overridden_at_entity_and_navigation_property_levels()
{
Expand Down
Loading

0 comments on commit caa5582

Please sign in to comment.