diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs index b44240263ed..3c273c8de95 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs @@ -234,6 +234,20 @@ public virtual PropertyBuilder IndexedProperty([NotNull] Type propertyType, [Not Check.NotNull(propertyType, nameof(propertyType)), Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit).Metadata); + /// + /// + /// Returns an object that can be used to configure an existing navigation property + /// from the owned type to its owner. It is an error for the navigation property + /// not to exist. + /// + /// + /// The name of the navigation property to be configured. + /// An object that can be used to configure the navigation property. + public virtual NavigationBuilder Navigation([NotNull] string navigationName) + => new NavigationBuilder( + DependentEntityType.Builder.Navigation( + Check.NotEmpty(navigationName, nameof(navigationName)))); + /// /// Excludes the given property from the entity type. This method is typically used to remove properties /// or navigations from the owned entity type that were added by convention. diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index cdf7eff8b29..edf2f5d20dd 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -91,6 +91,23 @@ public virtual PropertyBuilder Property( Check.NotNull(propertyExpression, nameof(propertyExpression)).GetPropertyAccess(), ConfigurationSource.Explicit).Metadata)); + /// + /// + /// Returns an object that can be used to configure an existing navigation property + /// from the owned type to its owner. It is an error for the navigation property + /// not to exist. + /// + /// + /// + /// A lambda expression representing the navigation property to be configured ( + /// blog => blog.Posts). + /// + /// An object that can be used to configure the navigation property. + public virtual NavigationBuilder Navigation( + [NotNull] Expression> navigationExpression) + => new NavigationBuilder(DependentEntityType.Builder.Navigation( + Check.NotNull(navigationExpression, nameof(navigationExpression)).GetPropertyAccess())); + /// /// Excludes the given property from the entity type. This method is typically used to remove properties /// or navigations from the owned entity type that were added by convention. diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index dfaec47500b..a9ebbddbaa8 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -226,6 +226,14 @@ public override TestIndexBuilder HasIndex(params string[] propertyNames) public override TestOwnedNavigationBuilder OwnsOne(string navigationName) => new GenericTestOwnedNavigationBuilder(EntityTypeBuilder.OwnsOne(navigationName)); + public override TestEntityTypeBuilder OwnsOne( + string navigationName, + Action> buildAction) + => Wrap( + EntityTypeBuilder.OwnsOne( + navigationName, + r => buildAction(new GenericTestOwnedNavigationBuilder(r)))); + public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) => new GenericTestOwnedNavigationBuilder(EntityTypeBuilder.OwnsOne(navigationExpression)); @@ -241,6 +249,13 @@ public override TestEntityTypeBuilder OwnsOne( public override TestOwnedNavigationBuilder OwnsMany(string navigationName) => new GenericTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsMany(navigationName)); + public override TestEntityTypeBuilder OwnsMany( + string navigationName, + Action> buildAction) + => Wrap( + EntityTypeBuilder.OwnsMany( + navigationName, + r => buildAction(new GenericTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) @@ -729,6 +744,12 @@ public override TestPropertyBuilder Property( Expression> propertyExpression) => new GenericTestPropertyBuilder(OwnedNavigationBuilder.Property(propertyExpression)); + public override TestNavigationBuilder Navigation(string navigationName) + => new GenericTestNavigationBuilder(OwnedNavigationBuilder.Navigation(navigationName)); + public override TestNavigationBuilder Navigation( + Expression> navigationExpression) + => new GenericTestNavigationBuilder(OwnedNavigationBuilder.Navigation(navigationExpression)); + public override TestOwnedNavigationBuilder Ignore(string propertyName) => Wrap(OwnedNavigationBuilder.Ignore(propertyName)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 1e0496d07f2..08325877606 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -173,6 +173,14 @@ public override TestIndexBuilder HasIndex(params string[] propertyNames) public override TestOwnedNavigationBuilder OwnsOne(string navigationName) => new NonGenericTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsOne(typeof(TRelatedEntity), navigationName)); + public override TestEntityTypeBuilder OwnsOne( + string navigationName, + Action> buildAction) + => Wrap( + EntityTypeBuilder.OwnsOne( + typeof(TRelatedEntity), + navigationName, + r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) @@ -191,6 +199,14 @@ public override TestEntityTypeBuilder OwnsOne( public override TestOwnedNavigationBuilder OwnsMany(string navigationName) => new NonGenericTestOwnedNavigationBuilder( EntityTypeBuilder.OwnsMany(typeof(TRelatedEntity), navigationName)); + public override TestEntityTypeBuilder OwnsMany( + string navigationName, + Action> buildAction) + => Wrap( + EntityTypeBuilder.OwnsMany( + typeof(TRelatedEntity), + navigationName, + r => buildAction(new NonGenericTestOwnedNavigationBuilder(r)))); public override TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) @@ -407,18 +423,18 @@ public override TestPropertyBuilder HasConversion(ValueConverter conv protected class NonGenericTestNavigationBuilder : TestNavigationBuilder { - public NonGenericTestNavigationBuilder(NavigationBuilder navigationIdentityBuilder) + public NonGenericTestNavigationBuilder(NavigationBuilder navigationBuilder) { - NavigationIdentityBuilder = navigationIdentityBuilder; + NavigationBuilder = navigationBuilder; } - private NavigationBuilder NavigationIdentityBuilder { get; } + private NavigationBuilder NavigationBuilder { get; } public override TestNavigationBuilder HasAnnotation(string annotation, object value) - => new NonGenericTestNavigationBuilder(NavigationIdentityBuilder.HasAnnotation(annotation, value)); + => new NonGenericTestNavigationBuilder(NavigationBuilder.HasAnnotation(annotation, value)); public override TestNavigationBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) - => new NonGenericTestNavigationBuilder(NavigationIdentityBuilder.UsePropertyAccessMode(propertyAccessMode)); + => new NonGenericTestNavigationBuilder(NavigationBuilder.UsePropertyAccessMode(propertyAccessMode)); } protected class @@ -725,6 +741,13 @@ public override TestPropertyBuilder Property( OwnedNavigationBuilder.Property(propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName())); } + public override TestNavigationBuilder Navigation(string navigationName) + => new NonGenericTestNavigationBuilder(OwnedNavigationBuilder.Navigation(navigationName)); + public override TestNavigationBuilder Navigation( + Expression> navigationExpression) + => new NonGenericTestNavigationBuilder( + OwnedNavigationBuilder.Navigation(navigationExpression.GetPropertyAccess().GetSimpleMemberName())); + public override TestOwnedNavigationBuilder Ignore(string propertyName) => Wrap(OwnedNavigationBuilder.Ignore(propertyName)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 95f9105e6ec..e135b25521d 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -208,6 +208,11 @@ public abstract TestEntityTypeBuilder Ignore( public abstract TestOwnedNavigationBuilder OwnsOne(string navigationName) where TRelatedEntity : class; + public abstract TestEntityTypeBuilder OwnsOne( + string navigationName, + Action> buildAction) + where TRelatedEntity : class; + public abstract TestOwnedNavigationBuilder OwnsOne( Expression> navigationExpression) where TRelatedEntity : class; @@ -219,6 +224,11 @@ public abstract TestEntityTypeBuilder OwnsOne( public abstract TestOwnedNavigationBuilder OwnsMany(string navigationName) where TRelatedEntity : class; + public abstract TestEntityTypeBuilder OwnsMany( + string navigationName, + Action> buildAction) + where TRelatedEntity : class; + public abstract TestOwnedNavigationBuilder OwnsMany( Expression>> navigationExpression) where TRelatedEntity : class; @@ -521,6 +531,10 @@ public abstract TestOwnedNavigationBuilder HasAnnotat public abstract TestPropertyBuilder Property( Expression> propertyExpression); + public abstract TestNavigationBuilder Navigation(string navigationName); + public abstract TestNavigationBuilder Navigation( + Expression> navigationExpression); + public abstract TestOwnedNavigationBuilder Ignore(string propertyName); public abstract TestOwnedNavigationBuilder Ignore( diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 693a223d46f..00766257b37 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -1331,77 +1331,111 @@ public virtual void Inheritance_where_base_has_multiple_owned_types_works() } [ConditionalFact] - public virtual void Principal_end_of_navigations_to_OneToOne_Owned_types_can_set_access_mode_using_expressions() + public virtual void Navigations_on_OwnsOne_Owned_types_can_set_access_mode_using_expressions() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity() - .OwnsOne(e => e.OwnedDependent) - .WithOwner(e => e.OneToOneOwner); + .OwnsOne( + e => e.OwnedDependent, + a => + { + a.WithOwner(owned => owned.OneToOneOwner); + a.Navigation(owned => owned.OneToOneOwner) + .UsePropertyAccessMode(PropertyAccessMode.Property); + }); modelBuilder.Entity() .Navigation(e => e.OwnedDependent) .UsePropertyAccessMode(PropertyAccessMode.Field); var principal = (IEntityType)model.FindEntityType(typeof(OneToOneNavPrincipalOwner)); + var dependent = (IEntityType)model.FindEntityType(typeof(OwnedNavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("OwnedDependent").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToOneOwner").GetPropertyAccessMode()); } [ConditionalFact] - public virtual void Principal_end_of_navigations_to_OneToOne_Owned_types_can_set_access_mode_using_navigation_names() + public virtual void Navigations_on_OwnsOne_Owned_types_can_set_access_mode_using_navigation_names() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity() - .OwnsOne("OwnedDependent") - .WithOwner("OneToOneOwner"); + .OwnsOne( + "OwnedDependent", + a => + { + a.WithOwner("OneToOneOwner"); + a.Navigation("OneToOneOwner") + .UsePropertyAccessMode(PropertyAccessMode.Property); + }); modelBuilder.Entity() .Navigation("OwnedDependent") .UsePropertyAccessMode(PropertyAccessMode.Field); var principal = (IEntityType)model.FindEntityType(typeof(OneToOneNavPrincipalOwner)); + var dependent = (IEntityType)model.FindEntityType(typeof(OwnedNavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("OwnedDependent").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToOneOwner").GetPropertyAccessMode()); } [ConditionalFact] - public virtual void Principal_end_of_navigations_to_OneToMany_Owned_types_can_set_access_mode_using_expressions() + public virtual void Navigations_on_OwnsMany_Owned_types_can_set_access_mode_using_expressions() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity() - .OwnsMany(e => e.OwnedDependents); + .OwnsMany( + e => e.OwnedDependents, + a => + { + a.WithOwner(owned => owned.OneToManyOwner); + a.Navigation(owned => owned.OneToManyOwner) + .UsePropertyAccessMode(PropertyAccessMode.Property); + }); modelBuilder.Entity() .Navigation(e => e.OwnedDependents) .UsePropertyAccessMode(PropertyAccessMode.Field); var principal = (IEntityType)model.FindEntityType(typeof(OneToManyNavPrincipalOwner)); + var dependent = (IEntityType)model.FindEntityType(typeof(OwnedOneToManyNavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("OwnedDependents").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToManyOwner").GetPropertyAccessMode()); } [ConditionalFact] - public virtual void Principal_end_of_navigations_to_OneToMany_Owned_types_can_set_access_mode_using_navigation_names() + public virtual void Navigations_on_OwnsMany_Owned_types_can_set_access_mode_using_navigation_names() { var modelBuilder = CreateModelBuilder(); var model = modelBuilder.Model; modelBuilder.Entity() - .OwnsMany("OwnedDependents"); + .OwnsMany( + "OwnedDependents", + a => + { + a.WithOwner("OneToManyOwner"); + a.Navigation("OneToManyOwner") + .UsePropertyAccessMode(PropertyAccessMode.Property); + }); modelBuilder.Entity() .Navigation("OwnedDependents") .UsePropertyAccessMode(PropertyAccessMode.Field); var principal = (IEntityType)model.FindEntityType(typeof(OneToManyNavPrincipalOwner)); + var dependent = (IEntityType)model.FindEntityType(typeof(OwnedOneToManyNavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindNavigation("OwnedDependents").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, dependent.FindNavigation("OneToManyOwner").GetPropertyAccessMode()); } } }