diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index bf4d2723bf6..58e212ab021 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -82,12 +82,25 @@ public InternalModelBuilder([NotNull] Model metadata) } if (shouldBeOwned == true - && entityType != null - && !entityType.IsOwned() - && configurationSource == ConfigurationSource.Explicit - && entityType.GetConfigurationSource() == ConfigurationSource.Explicit) + && entityType != null) { - throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(entityType.DisplayName())); + if (!entityType.IsOwned() + && configurationSource == ConfigurationSource.Explicit + && entityType.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(entityType.DisplayName())); + } + + foreach (var derivedType in entityType.GetDerivedTypes()) + { + if (!derivedType.IsOwned() + && configurationSource == ConfigurationSource.Explicit + && derivedType.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.ClashingNonOwnedDerivedEntityType(entityType.DisplayName(), derivedType.DisplayName())); + } + } } if (entityType != null) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 0524bd06596..126b1469076 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -2198,6 +2198,14 @@ public static string IncludeOnEntityWithDefiningQueryNotSupported([CanBeNull] ob GetString("IncludeOnEntityWithDefiningQueryNotSupported", nameof(entityType)), entityType); + /// + /// The type '{entityType}' cannot be marked as owned because the derived entity type - '{derivedType}' has been configured as non-owned. + /// + public static string ClashingNonOwnedDerivedEntityType([CanBeNull] object entityType, [CanBeNull] object derivedType) + => string.Format( + GetString("ClashingNonOwnedDerivedEntityType", nameof(entityType), nameof(derivedType)), + entityType, derivedType); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 26b5a6527d6..b10482078ae 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1219,4 +1219,7 @@ '{principalEntityType}.{principalNavigation}' may still be null at runtime despite being declared as non-nullable since only the navigation to principal can be configured as required. Debug CoreEventId.NonNullableReferenceOnDependent string string - + + The type '{entityType}' cannot be marked as owned because the derived entity type - '{derivedType}' has been configured as non-owned. + + \ No newline at end of file diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 1c9280cb390..a4650fac077 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -157,12 +157,7 @@ public TestModelBuilder HasAnnotation(string annotation, object value) public abstract TestModelBuilder Ignore() where TEntity : class; - public virtual TestModelBuilder FinalizeModel() - { - ModelBuilder.FinalizeModel(); - - return this; - } + public virtual IModel FinalizeModel() => ModelBuilder.FinalizeModel(); public virtual string GetDisplayName(Type entityType) => entityType.Name; diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index fd17645677f..765ad3181d0 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -1129,6 +1129,75 @@ public virtual void Reconfiguring_owned_type_as_non_owned_throws() modelBuilder.Entity().HasOne(c => c.Details)).Message); } + [ConditionalFact] + public virtual void Deriving_from_owned_type_throws() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Ignore(b => b.AlternateLabel) + .Ignore(b => b.Details) + .OwnsOne(b => b.Label, lb => + { + lb.Ignore(l => l.AnotherBookLabel); + lb.Ignore(l => l.SpecialBookLabel); + }); + + Assert.Equal( + CoreStrings.ClashingOwnedEntityType(nameof(AnotherBookLabel)), + Assert.Throws( + () => modelBuilder.Entity()).Message); + } + + [ConditionalFact] + public virtual void Configuring_base_type_as_owned_throws() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + + modelBuilder.Entity() + .Ignore(b => b.AlternateLabel) + .Ignore(b => b.Details); + + Assert.Equal( + CoreStrings.ClashingNonOwnedDerivedEntityType(nameof(BookLabel), nameof(AnotherBookLabel)), + Assert.Throws( + () => + modelBuilder.Entity().OwnsOne(c => c.Label)).Message); + } + + [ConditionalFact] + public virtual void CLR_base_type_can_be_owned_when_not_in_hierarchy() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .HasBaseType(null) + .Ignore(l => l.Book) + .Ignore(l => l.SpecialBookLabel) + .Ignore(l => l.AnotherBookLabel); + + modelBuilder.Entity() + .Ignore(b => b.AlternateLabel) + .Ignore(b => b.Details) + .OwnsOne(c => c.Label, lb => + { + lb.Ignore(l => l.AnotherBookLabel); + lb.Ignore(l => l.SpecialBookLabel); + }); + + var model = modelBuilder.FinalizeModel(); + + var bookLabelOwnership = model.FindEntityType(typeof(Book)).FindNavigation(nameof(Book.Label)) + .ForeignKey; + + Assert.True(bookLabelOwnership.IsOwnership); + Assert.Equal(nameof(BookLabel.Book), bookLabelOwnership.DependentToPrincipal.Name); + + Assert.Null(model.FindEntityType(typeof(AnotherBookLabel)).BaseType); + } + [ConditionalFact] public virtual void OwnedType_can_derive_from_Collection() {