diff --git a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs index b06d074c5f1..4f0f86fad55 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs @@ -25,7 +25,7 @@ public static class CosmosEntityTypeExtensions ?? GetDefaultContainer(entityType); private static string? GetDefaultContainer(IReadOnlyEntityType entityType) - => entityType.IsOwned() + => entityType.FindOwnership() != null ? null : entityType.Model.GetDefaultContainer() ?? entityType.ShortName(); diff --git a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs index d4528ba6aef..b9b866715ee 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index 471753c8058..037851d599e 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -1,12 +1,11 @@ // 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 System.Linq; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Utilities; +// ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Metadata.Conventions { /// @@ -16,7 +15,8 @@ public class CosmosDiscriminatorConvention : DiscriminatorConvention, IForeignKeyOwnershipChangedConvention, IForeignKeyRemovedConvention, - IEntityTypeAddedConvention + IEntityTypeAddedConvention, + IEntityTypeAnnotationChangedConvention { /// /// Creates a new instance of . @@ -36,16 +36,7 @@ public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - Check.NotNull(context, nameof(context)); - - var entityType = entityTypeBuilder.Metadata; - if (entityType.BaseType == null - && entityType.IsDocumentRoot()) - { - entityTypeBuilder.HasDiscriminator(typeof(string)) - ?.HasValue(entityType, entityType.ShortName()); - } + ProcessEntityType(entityTypeBuilder); } /// @@ -57,17 +48,9 @@ public virtual void ProcessForeignKeyOwnershipChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) { - Check.NotNull(relationshipBuilder, nameof(relationshipBuilder)); - Check.NotNull(context, nameof(context)); - var entityType = relationshipBuilder.Metadata.DeclaringEntityType; - if (relationshipBuilder.Metadata.IsOwnership - && !entityType.IsDocumentRoot() - && entityType.BaseType == null - && !entityType.GetDerivedTypes().Any()) - { - entityType.Builder.HasNoDiscriminator(); - } + + ProcessEntityType(entityType.Builder); } /// @@ -81,13 +64,52 @@ public virtual void ProcessForeignKeyRemoved( IConventionForeignKey foreignKey, IConventionContext context) { - var entityType = foreignKey.DeclaringEntityType; - if (foreignKey.IsOwnership - && !entityType.IsDocumentRoot() - && entityType.BaseType == null - && !entityType.GetDerivedTypes().Any()) + if (foreignKey.IsOwnership) { - entityType.Builder.HasNoDiscriminator(); + ProcessEntityType(entityTypeBuilder); + } + } + + /// + /// Called after an annotation is changed on an entity type. + /// + /// The builder for the entity type. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + public void ProcessEntityTypeAnnotationChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context) + { + if (name != CosmosAnnotationNames.ContainerName + || (annotation == null) == (oldAnnotation == null)) + { + return; + } + + ProcessEntityType(entityTypeBuilder); + } + + private void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBuilder) + { + var entityType = entityTypeBuilder.Metadata; + if (entityType.BaseType != null) + { + return; + } + + if (!entityType.IsDocumentRoot()) + { + entityTypeBuilder.HasNoDiscriminator(); + } + else + { + entityTypeBuilder.HasDiscriminator(typeof(string)) + ?.HasValue(entityType, entityType.ShortName()); } } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosInversePropertyAttributeConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosInversePropertyAttributeConvention.cs new file mode 100644 index 00000000000..630e7c5f418 --- /dev/null +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosInversePropertyAttributeConvention.cs @@ -0,0 +1,52 @@ +// 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 System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that configures the inverse navigation property based on the + /// specified on the other navigation property. + /// All navigations are assumed to be targeting owned entity types for Cosmos. + /// + public class CosmosInversePropertyAttributeConvention : InversePropertyAttributeConvention + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public CosmosInversePropertyAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + /// Finds or tries to create an entity type target for the given navigation member. + /// + /// The builder for the referencing entity type. + /// The CLR type of the target entity type. + /// The navigation member. + /// Whether an entity type should be created if one doesn't currently exist. + /// The builder for the target entity type or if it can't be created. + protected override IConventionEntityTypeBuilder? TryGetTargetEntityTypeBuilder( + IConventionEntityTypeBuilder entityTypeBuilder, + Type targetClrType, + MemberInfo navigationMemberInfo, + bool shouldCreate = true) + => ((InternalEntityTypeBuilder)entityTypeBuilder) +#pragma warning disable EF1001 // Internal EF Core API usage. + .GetTargetEntityTypeBuilder( + targetClrType, + navigationMemberInfo, + shouldCreate ? ConfigurationSource.DataAnnotation : null, + targetShouldBeOwned: true); +#pragma warning restore EF1001 // Internal EF Core API usage. + } +} diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs new file mode 100644 index 00000000000..9d7932aa58d --- /dev/null +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosRelationshipDiscoveryConvention.cs @@ -0,0 +1,35 @@ +// 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 System; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions +{ + /// + /// A convention that configures relationships between entity types based on the navigation properties + /// as long as there is no ambiguity as to which is the corresponding inverse navigation. + /// All navigations are assumed to be targeting owned entity types for Cosmos. + /// + public class CosmosRelationshipDiscoveryConvention : RelationshipDiscoveryConvention + { + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public CosmosRelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + /// Returns a value indicating whether the given entity type should be added as owned if it isn't currently in the model. + /// + /// Target entity type. + /// The model. + /// if the given entity type should be owned. + protected override bool? ShouldBeOwned(Type targetType, IConventionModel model) + => true; + } +} diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs index c80c12f9096..be44180f560 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs @@ -44,47 +44,71 @@ public override ConventionSet CreateConventionSet() var storeKeyConvention = new StoreKeyConvention(Dependencies); var discriminatorConvention = new CosmosDiscriminatorConvention(Dependencies); - var keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies); + KeyDiscoveryConvention keyDiscoveryConvention = new CosmosKeyDiscoveryConvention(Dependencies); + InversePropertyAttributeConvention inversePropertyAttributeConvention = + new CosmosInversePropertyAttributeConvention(Dependencies); + RelationshipDiscoveryConvention relationshipDiscoveryConvention = + new CosmosRelationshipDiscoveryConvention(Dependencies); conventionSet.EntityTypeAddedConventions.Add(storeKeyConvention); conventionSet.EntityTypeAddedConventions.Add(discriminatorConvention); - ReplaceConvention(conventionSet.EntityTypeAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeAddedConventions, keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeAddedConventions, inversePropertyAttributeConvention); + ReplaceConvention(conventionSet.EntityTypeAddedConventions, relationshipDiscoveryConvention); + + ReplaceConvention(conventionSet.EntityTypeIgnoredConventions, relationshipDiscoveryConvention); ReplaceConvention(conventionSet.EntityTypeRemovedConventions, (DiscriminatorConvention)discriminatorConvention); + ReplaceConvention(conventionSet.EntityTypeRemovedConventions, inversePropertyAttributeConvention); conventionSet.EntityTypeBaseTypeChangedConventions.Add(storeKeyConvention); ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (DiscriminatorConvention)discriminatorConvention); - ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, inversePropertyAttributeConvention); + ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, relationshipDiscoveryConvention); - ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, keyDiscoveryConvention); + ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, inversePropertyAttributeConvention); + ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, relationshipDiscoveryConvention); conventionSet.EntityTypePrimaryKeyChangedConventions.Add(storeKeyConvention); conventionSet.KeyAddedConventions.Add(storeKeyConvention); conventionSet.KeyRemovedConventions.Add(storeKeyConvention); - ReplaceConvention(conventionSet.KeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.KeyRemovedConventions, keyDiscoveryConvention); - ReplaceConvention(conventionSet.ForeignKeyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyAddedConventions, keyDiscoveryConvention); conventionSet.ForeignKeyRemovedConventions.Add(discriminatorConvention); conventionSet.ForeignKeyRemovedConventions.Add(storeKeyConvention); - ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyRemovedConventions, keyDiscoveryConvention); - ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, keyDiscoveryConvention); - ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyUniquenessChangedConventions, keyDiscoveryConvention); conventionSet.ForeignKeyOwnershipChangedConventions.Add(discriminatorConvention); conventionSet.ForeignKeyOwnershipChangedConventions.Add(storeKeyConvention); - ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, keyDiscoveryConvention); + ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, relationshipDiscoveryConvention); + + ReplaceConvention(conventionSet.ForeignKeyNullNavigationSetConventions, relationshipDiscoveryConvention); + + ReplaceConvention(conventionSet.NavigationAddedConventions, inversePropertyAttributeConvention); + ReplaceConvention(conventionSet.NavigationAddedConventions, relationshipDiscoveryConvention); + ReplaceConvention(conventionSet.NavigationRemovedConventions, relationshipDiscoveryConvention); + + conventionSet.EntityTypeAnnotationChangedConventions.Add(discriminatorConvention); conventionSet.EntityTypeAnnotationChangedConventions.Add(storeKeyConvention); - conventionSet.EntityTypeAnnotationChangedConventions.Add(keyDiscoveryConvention); + conventionSet.EntityTypeAnnotationChangedConventions.Add((CosmosKeyDiscoveryConvention)keyDiscoveryConvention); - ReplaceConvention(conventionSet.PropertyAddedConventions, (KeyDiscoveryConvention)keyDiscoveryConvention); + ReplaceConvention(conventionSet.PropertyAddedConventions, keyDiscoveryConvention); conventionSet.PropertyAnnotationChangedConventions.Add(storeKeyConvention); + ReplaceConvention(conventionSet.ModelFinalizingConventions, inversePropertyAttributeConvention); + return conventionSet; } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs index 63115c4bb0b..8120e1eebce 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs @@ -5,7 +5,6 @@ using System.Linq; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration; -using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs index a8af6bf82e5..154997c2b79 100644 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs +++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosEntityTypeExtensions.cs @@ -21,7 +21,7 @@ public static class CosmosEntityTypeExtensions /// public static bool IsDocumentRoot(this IReadOnlyEntityType entityType) => entityType.BaseType?.IsDocumentRoot() - ?? (!entityType.IsOwned() + ?? (entityType.FindOwnership() == null || entityType[CosmosAnnotationNames.ContainerName] != null); } } diff --git a/src/EFCore.Relational/Metadata/IColumn.cs b/src/EFCore.Relational/Metadata/IColumn.cs index ec6c3d29011..0c7dcbf6fa0 100644 --- a/src/EFCore.Relational/Metadata/IColumn.cs +++ b/src/EFCore.Relational/Metadata/IColumn.cs @@ -90,7 +90,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue) return false; } - var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping?.Converter; + var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter; if (converter != null) { diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index c6781c0c670..2f1ef8bb89c 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -221,7 +221,7 @@ protected virtual void ValidatePropertyMapping( if (targetType != null) { var targetShared = conventionModel.IsShared(targetType); - targetOwned ??= conventionModel.IsOwned(targetType); + targetOwned ??= IsOwned(targetType, conventionModel); // ReSharper disable CheckForReferenceEqualityInstead.1 // ReSharper disable CheckForReferenceEqualityInstead.3 if ((!entityType.IsKeyless @@ -230,10 +230,9 @@ protected virtual void ValidatePropertyMapping( dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName()) == null) && (!(targetShared || targetOwned.Value) - || (!targetType.Equals(entityType.ClrType) - && (!entityType.IsInOwnershipPath(targetType) - || (entityType.FindOwnership()!.PrincipalEntityType.ClrType.Equals(targetType) - && targetSequenceType == null))))) + || !targetType.Equals(entityType.ClrType)) + && (!entityType.IsInOwnershipPath(targetType) + || targetSequenceType == null)) { if (entityType.IsOwned() && targetOwned.Value) @@ -274,6 +273,16 @@ protected virtual void ValidatePropertyMapping( } } + /// + /// Returns a value indicating whether that target CLR type would correspond to an owned entity type. + /// + /// The target CLR type. + /// The model. + /// if the given CLR type corresponds to an owned entity type. + protected virtual bool IsOwned(Type targetType, IConventionModel conventionModel) + => conventionModel.FindIsOwnedConfigurationSource(targetType) != null + || conventionModel.FindEntityTypes(targetType).Any(t => t.IsOwned()); + /// /// Validates that no attempt is made to ignore inherited properties. /// diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs index f67ab7e4a3a..cef8c153dde 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs @@ -17,6 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders public class OwnedNavigationBuilder : IInfrastructure { private InternalForeignKeyBuilder _builder; + private EntityType _dependentEntityType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -28,7 +29,7 @@ public class OwnedNavigationBuilder : IInfrastructure [EntityFrameworkInternal] - protected virtual EntityType DependentEntityType { get; } + protected virtual EntityType DependentEntityType + => _dependentEntityType.IsInModel + ? _dependentEntityType + : _dependentEntityType = Builder.Metadata.DeclaringEntityType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -55,7 +59,8 @@ protected virtual InternalForeignKeyBuilder Builder { get { - if (!_builder.Metadata.IsInModel) + if (!_builder.Metadata.IsInModel + && PrincipalEntityType.IsInModel) { _builder = PrincipalEntityType.FindNavigation(_builder.Metadata.PrincipalToDependent!.Name)?.ForeignKey.Builder!; } diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs index 9923162272b..a52fb52152d 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs @@ -1125,7 +1125,7 @@ public virtual ReferenceNavigationBuilder H navigation, DependentEntityType.Builder.HasRelationship( relatedEntityType, navigation, ConfigurationSource.Explicit, - targetIsPrincipal: DependentEntityType == relatedEntityType ? true : (bool?)null)!.Metadata); + targetIsPrincipal: DependentEntityType == relatedEntityType ? true : null)!.Metadata); } /// diff --git a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs index cd8e2680236..f9a2effc41f 100644 --- a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs @@ -121,7 +121,8 @@ private void Process( targetEntityType, targetEntityType.BaseType, inverseNavigationPropertyInfo, - referencingNavigationsWithAttribute, out var conventionForeignKeyBuilder)) + referencingNavigationsWithAttribute, + out var conventionForeignKeyBuilder)) { return conventionForeignKeyBuilder; } @@ -139,13 +140,46 @@ private void Process( return null; } + var targetOwnership = targetEntityType.FindOwnership(); + if (targetOwnership != null + && targetOwnership.PrincipalEntityType == entityType + && targetOwnership.PrincipalToDependent?.GetIdentifyingMemberInfo() != navigationMemberInfo) + { + Dependencies.Logger.NonOwnershipInverseNavigationWarning( + entityType, navigationMemberInfo, + targetEntityType, inverseNavigationPropertyInfo, + targetOwnership.PrincipalToDependent?.GetIdentifyingMemberInfo()!); + + return null; + } + + if (targetEntityType.IsOwned() + && (targetOwnership == null + || targetOwnership.PrincipalEntityType == entityType)) + { + if (navigationMemberInfo.DeclaringType != entityType.ClrType + && (entityType.Model.FindEntityType(navigationMemberInfo.DeclaringType!) != null + || (navigationMemberInfo.DeclaringType != entityType.ClrType.BaseType + && entityType.Model.FindEntityType(entityType.ClrType.BaseType!) != null))) + { + return null; + } + + return entityTypeBuilder.HasOwnership( + targetEntityType, + navigationMemberInfo, + inverseNavigationPropertyInfo, + fromDataAnnotation: true); + } + if (entityType.IsOwned() - && !entityType.IsInOwnershipPath(targetEntityType.ClrType)) + && (ownership == null + || ownership.PrincipalEntityType == targetEntityType)) { if (navigationMemberInfo.DeclaringType != entityType.ClrType - && (entityType.Model.FindEntityTypes(navigationMemberInfo.DeclaringType!).Any() + && (entityType.Model.FindEntityType(navigationMemberInfo.DeclaringType!) != null || (navigationMemberInfo.DeclaringType != entityType.ClrType.BaseType - && entityType.Model.FindEntityTypes(entityType.ClrType.BaseType!).Any()))) + && entityType.Model.FindEntityType(entityType.ClrType.BaseType!) != null))) { return null; } @@ -157,6 +191,12 @@ private void Process( fromDataAnnotation: true); } + if (ownership != null + || targetOwnership != null) + { + return null; + } + var newForeignKeyBuilder = targetEntityTypeBuilder.HasRelationship( entityType, inverseNavigationPropertyInfo, @@ -189,7 +229,7 @@ private static bool TryRemoveIfAmbiguous( IConventionEntityType? targetBaseType, MemberInfo inverseNavigationMemberInfo, List<(MemberInfo, IConventionEntityType)> referencingNavigationsWithAttribute, - out IConventionForeignKeyBuilder? configureInverseNavigation) + out IConventionForeignKeyBuilder? remainingInverseNavigation) { var ambiguousInverse = FindAmbiguousInverse( navigationMemberInfo, entityType, referencingNavigationsWithAttribute); @@ -243,70 +283,73 @@ private static bool TryRemoveIfAmbiguous( existingAmbiguousNavigation, fromDataAnnotation: true); } - { - configureInverseNavigation = entityType.FindSkipNavigation(navigationMemberInfo)?.ForeignKey!.Builder; - return true; - } + remainingInverseNavigation = entityType.FindSkipNavigation(navigationMemberInfo)?.ForeignKey!.Builder; + return true; } else { var existingInverse = targetEntityType.FindNavigation(inverseNavigationMemberInfo)?.Inverse; - var existingInverseType = existingInverse?.DeclaringEntityType; if (existingInverse != null && IsAmbiguousInverse( - existingInverse.GetIdentifyingMemberInfo()!, existingInverseType!, referencingNavigationsWithAttribute)) + existingInverse.GetIdentifyingMemberInfo()!, + existingInverse.DeclaringEntityType, + referencingNavigationsWithAttribute)) { - var fk = existingInverse.ForeignKey; - if (fk.IsOwnership - || fk.DeclaringEntityType.Builder.HasNoRelationship(fk, fromDataAnnotation: true) == null) - { - fk.Builder.HasNavigation( - (string?)null, - existingInverse.IsOnDependent, - fromDataAnnotation: true); - } + Remove(existingInverse); } var existingNavigation = entityType.FindNavigation(navigationMemberInfo); if (existingNavigation != null) { - var fk = existingNavigation.ForeignKey; - if (fk.IsOwnership - || fk.DeclaringEntityType.Builder.HasNoRelationship(fk, fromDataAnnotation: true) == null) - { - fk.Builder.HasNavigation( - (string?)null, - existingNavigation.IsOnDependent, - fromDataAnnotation: true); - } + Remove(existingNavigation); } var existingAmbiguousNavigation = FindActualEntityType(ambiguousInverse.Value.Item2)! .FindNavigation(ambiguousInverse.Value.Item1); if (existingAmbiguousNavigation != null) { - var fk = existingAmbiguousNavigation.ForeignKey; - if (fk.IsOwnership - || fk.DeclaringEntityType.Builder.HasNoRelationship(fk, fromDataAnnotation: true) == null) - { - fk.Builder.HasNavigation( - (string?)null, - existingAmbiguousNavigation.IsOnDependent, - fromDataAnnotation: true); - } + Remove(existingAmbiguousNavigation); } - { - configureInverseNavigation = entityType.FindNavigation(navigationMemberInfo)?.ForeignKey.Builder; - return true; - } + remainingInverseNavigation = entityType.FindNavigation(navigationMemberInfo)?.ForeignKey.Builder; + return true; } } - configureInverseNavigation = null; + remainingInverseNavigation = null; return false; } + private static void Remove(IConventionNavigation navigation) + { + var foreignKey = navigation.ForeignKey; + if (foreignKey.IsOwnership) + { + if (navigation.IsOnDependent) + { + foreignKey.Builder.HasNavigation( + (string?)null, + navigation.IsOnDependent, + fromDataAnnotation: true); + } + else if (ConfigurationSource.DataAnnotation.Overrides(foreignKey.DeclaringEntityType.GetConfigurationSource())) + { + navigation.DeclaringEntityType.Model.Builder.HasNoEntityType(foreignKey.DeclaringEntityType, fromDataAnnotation: true); + } + else + { + foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, fromDataAnnotation: true); + } + } + else if (foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, fromDataAnnotation: true) == null) + { + foreignKey.Builder.HasNavigation( + (string?)null, + navigation.IsOnDependent, + fromDataAnnotation: true); + } + } + /// public override void ProcessEntityTypeRemoved( IConventionModelBuilder modelBuilder, @@ -319,7 +362,7 @@ public override void ProcessEntityTypeRemoved( var targetEntityType = modelBuilder.Metadata.FindEntityType(targetClrType); if (targetEntityType != null) { - RemoveInverseNavigation(entityType.ClrType, navigationMemberInfo, targetEntityType, attribute.Property); + RemoveInverseNavigation(entityType, navigationMemberInfo, targetEntityType, attribute.Property); } var declaringType = navigationMemberInfo.DeclaringType; @@ -387,8 +430,8 @@ public override void ProcessEntityTypeBaseTypeChanged( InversePropertyAttribute attribute, IConventionContext context) { - var entityClrType = entityTypeBuilder.Metadata.ClrType; - if (navigationMemberInfo.DeclaringType != entityClrType) + var entityType = entityTypeBuilder.Metadata; + if (navigationMemberInfo.DeclaringType != entityType.ClrType) { if (newBaseType == null) { @@ -396,13 +439,13 @@ public override void ProcessEntityTypeBaseTypeChanged( } else { - var targetEntityType = entityTypeBuilder.Metadata.Model.FindEntityType(targetClrType); + var targetEntityType = entityType.Model.FindEntityType(targetClrType); if (targetEntityType == null) { return; } - RemoveInverseNavigation(entityClrType, navigationMemberInfo, targetEntityType, attribute.Property); + RemoveInverseNavigation(entityType, navigationMemberInfo, targetEntityType, attribute.Property); } } } @@ -468,7 +511,7 @@ public override void ProcessEntityTypeMemberIgnored( return; } - RemoveInverseNavigation(entityTypeBuilder.Metadata.ClrType, navigationMemberInfo, targetEntityType, attribute.Property); + RemoveInverseNavigation(entityTypeBuilder.Metadata, navigationMemberInfo, targetEntityType, attribute.Property); } /// @@ -650,11 +693,12 @@ private static (MemberInfo, IConventionEntityType)? FindAmbiguousInverse( } private static void RemoveInverseNavigation( - Type declaringType, + IConventionEntityType declaringEntityType, MemberInfo navigation, IConventionEntityType targetEntityType, string inverseNavigationName) { + var declaringType = declaringEntityType.ClrType; var inverseNavigations = GetInverseNavigations(targetEntityType); if (inverseNavigations == null || !inverseNavigations.TryGetValue(inverseNavigationName, out var inverseNavigationPair)) @@ -668,7 +712,10 @@ private static void RemoveInverseNavigation( { var referencingTuple = referencingNavigationsWithAttribute[index]; if (referencingTuple.Item1.IsSameAs(navigation) - && declaringType.IsAssignableFrom(referencingTuple.Item2.ClrType)) + && (!referencingTuple.Item2.IsInModel + && declaringType.IsAssignableFrom(referencingTuple.Item2.ClrType)) + || (referencingTuple.Item2.IsInModel + && declaringEntityType.IsAssignableFrom(referencingTuple.Item2))) { anyRemoved = true; referencingNavigationsWithAttribute.RemoveAt(index--); diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index 90fa0b8809b..0429e98d8b3 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -120,8 +120,6 @@ public virtual void ProcessEntityTypeRemoved( IConventionEntityType entityType, IConventionContext context) { - var type = entityType.ClrType; - var navigations = GetNavigationsWithAttribute(entityType); if (navigations == null) { diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index 915bd315b41..92ddd14340a 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -91,7 +91,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent { var targetType = relationshipCandidate.TargetTypeBuilder.Metadata; if (targetType.IsInModel - && IsImplicitlyCreatedUnusedSharedType(targetType)) + && IsImplicitlyCreatedUnusedType(targetType)) { targetType.Builder.ModelBuilder.HasNoEntityType(targetType); } @@ -117,7 +117,9 @@ private IReadOnlyList FindRelationshipCandidates(IConvent var shouldBeOwnership = candidateTargetEntityType.IsOwned() && (targetOwnership == null || (targetOwnership.PrincipalEntityType == entityType - && targetOwnership.PrincipalToDependent?.Name == navigationPropertyInfo.GetSimpleMemberName())); + && targetOwnership.PrincipalToDependent?.Name == navigationPropertyInfo.GetSimpleMemberName())) + && (ownership == null + || !entityType.IsInOwnershipPath(candidateTargetEntityType)); if (candidateTargetEntityType.IsOwned() && !shouldBeOwnership @@ -131,6 +133,14 @@ private IReadOnlyList FindRelationshipCandidates(IConvent continue; } + if (!shouldBeOwnership + && ownership != null + && navigationPropertyInfo.PropertyType != targetClrType) + { + // Don't try to configure a collection on an owned type unless it represents a sub-ownership + continue; + } + if (relationshipCandidates.TryGetValue(candidateTargetEntityType, out var existingCandidate)) { if (!existingCandidate.IsOwnership @@ -175,12 +185,15 @@ private IReadOnlyList FindRelationshipCandidates(IConvent } var inverseTargetType = inverseCandidateTuple.Value.Type; + var inverseIsCollection = inverseTargetType != inversePropertyInfo.PropertyType; if (inverseTargetType != entityType.ClrType && (!inverseTargetType.IsAssignableFrom(entityType.ClrType) + || inverseIsCollection || (!shouldBeOwnership && !candidateTargetEntityType.IsInOwnershipPath(entityType)))) { - // Only use inverse of a base type if the target is owned by the current entity type + // Only use inverse of a base type if the target is owned by the current entity type, + // unless it's a collection continue; } @@ -198,13 +211,13 @@ private IReadOnlyList FindRelationshipCandidates(IConvent } if (shouldBeOwnership - && inversePropertyInfo.PropertyType.TryGetSequenceType() != null + && inverseIsCollection && navigations.Count == 1) { // Target type should be the principal, discover the relationship from the other side var targetType = candidateTargetEntityType; if (targetType.IsInModel - && IsImplicitlyCreatedUnusedSharedType(targetType)) + && IsImplicitlyCreatedUnusedType(targetType)) { targetType.Builder.ModelBuilder.HasNoEntityType(targetType); } @@ -350,7 +363,7 @@ private static IReadOnlyList RemoveIncompatibleWithExisti PropertyInfo? compatibleInverse = null; foreach (var inverseProperty in relationshipCandidate.InverseProperties) { - if (IsCompatibleInverse( + if (AreCompatible( navigationProperty, inverseProperty, entityTypeBuilder, targetEntityTypeBuilder)) { if (compatibleInverse == null) @@ -387,6 +400,18 @@ private static IReadOnlyList RemoveIncompatibleWithExisti relationshipCandidate.InverseProperties.Remove(nextSelfRefCandidate); } + if (relationshipCandidate.NavigationProperties.Count == 0) + { + foreach (var inverseProperty in relationshipCandidate.InverseProperties.ToList()) + { + if (!AreCompatible( + null, inverseProperty, entityTypeBuilder, targetEntityTypeBuilder)) + { + relationshipCandidate.InverseProperties.Remove(inverseProperty); + } + } + } + continue; } @@ -394,7 +419,7 @@ private static IReadOnlyList RemoveIncompatibleWithExisti foreach (var otherNavigation in relationshipCandidate.NavigationProperties) { if (otherNavigation != navigationProperty - && IsCompatibleInverse(otherNavigation, compatibleInverse, entityTypeBuilder, targetEntityTypeBuilder)) + && AreCompatible(otherNavigation, compatibleInverse, entityTypeBuilder, targetEntityTypeBuilder)) { noOtherCompatibleNavigation = false; break; @@ -439,7 +464,7 @@ private static IReadOnlyList RemoveIncompatibleWithExisti { filteredRelationshipCandidates.Add(relationshipCandidate); } - else if (IsImplicitlyCreatedUnusedSharedType(relationshipCandidate.TargetTypeBuilder.Metadata) + else if (IsImplicitlyCreatedUnusedType(relationshipCandidate.TargetTypeBuilder.Metadata) && filteredRelationshipCandidates.All( c => c.TargetTypeBuilder.Metadata != relationshipCandidate.TargetTypeBuilder.Metadata)) { @@ -451,25 +476,39 @@ private static IReadOnlyList RemoveIncompatibleWithExisti return filteredRelationshipCandidates; } - private static bool IsCompatibleInverse( - PropertyInfo navigationProperty, - PropertyInfo inversePropertyInfo, + private static bool AreCompatible( + PropertyInfo? navigationProperty, + PropertyInfo? inversePropertyInfo, IConventionEntityTypeBuilder entityTypeBuilder, IConventionEntityTypeBuilder targetEntityTypeBuilder) { var entityType = entityTypeBuilder.Metadata; - var existingNavigation = entityType.FindNavigation(navigationProperty.GetSimpleMemberName()); - if (existingNavigation != null - && !CanMergeWith(existingNavigation, inversePropertyInfo, targetEntityTypeBuilder)) + if (navigationProperty != null) { - return false; + var existingNavigation = entityType.FindNavigation(navigationProperty.GetSimpleMemberName()); + if (existingNavigation != null + && ((inversePropertyInfo != null + && !CanSetInverse(existingNavigation, inversePropertyInfo, targetEntityTypeBuilder)) + || (!existingNavigation.TargetEntityType.IsAssignableFrom(targetEntityTypeBuilder.Metadata) + && !targetEntityTypeBuilder.Metadata.IsAssignableFrom(existingNavigation.TargetEntityType)))) + { + return false; + } + } + + if (inversePropertyInfo == null) + { + return true; } var existingInverse = targetEntityTypeBuilder.Metadata.FindNavigation(inversePropertyInfo.Name); if (existingInverse != null) { if (existingInverse.DeclaringEntityType != targetEntityTypeBuilder.Metadata - || !CanMergeWith(existingInverse, navigationProperty, entityTypeBuilder)) + || (navigationProperty != null + && !CanSetInverse(existingInverse, navigationProperty, entityTypeBuilder)) + || (!existingInverse.TargetEntityType.IsAssignableFrom(entityTypeBuilder.Metadata) + && !entityTypeBuilder.Metadata.IsAssignableFrom(existingInverse.TargetEntityType))) { return false; } @@ -484,7 +523,7 @@ private static bool IsCompatibleInverse( return true; } - private static bool CanMergeWith( + private static bool CanSetInverse( IConventionNavigation existingNavigation, MemberInfo inverse, IConventionEntityTypeBuilder inverseEntityTypeBuilder) @@ -585,7 +624,7 @@ private static IReadOnlyList RemoveSingleSidedBaseNavigat { filteredRelationshipCandidates.Add(relationshipCandidate); } - else if (IsImplicitlyCreatedUnusedSharedType(relationshipCandidate.TargetTypeBuilder.Metadata) + else if (IsImplicitlyCreatedUnusedType(relationshipCandidate.TargetTypeBuilder.Metadata) && filteredRelationshipCandidates.All( c => c.TargetTypeBuilder.Metadata != relationshipCandidate.TargetTypeBuilder.Metadata)) { @@ -611,6 +650,28 @@ private void CreateRelationships( || (targetEntityType.BaseType != null && HasAmbiguousNavigationsTo(targetEntityType.BaseType, entityType.ClrType)); + if (relationshipCandidate.InverseProperties.Count > 1 + && relationshipCandidate.IsOwnership) + { + Type? mostDerivedType = null; + foreach (var inverseProperty in relationshipCandidate.InverseProperties) + { + var inverseType = inverseProperty.GetMemberType(); + if (mostDerivedType == null) + { + mostDerivedType = inverseType; + } + else if (!inverseType.IsAssignableFrom(mostDerivedType) + && mostDerivedType.IsAssignableFrom(inverseType)) + { + mostDerivedType = inverseType; + } + } + + relationshipCandidate.InverseProperties.RemoveAll(p => + p.GetMemberType().IsAssignableFrom(mostDerivedType) && p.GetMemberType() != mostDerivedType); + } + if ((relationshipCandidate.NavigationProperties.Count > 1 && relationshipCandidate.InverseProperties.Count > 0 && !relationshipCandidate.IsOwnership) @@ -738,7 +799,7 @@ private void CreateRelationships( foreach (var unusedEntityType in unusedEntityTypes) { - if (IsImplicitlyCreatedUnusedSharedType(unusedEntityType)) + if (IsImplicitlyCreatedUnusedType(unusedEntityType)) { entityTypeBuilder.ModelBuilder.HasNoEntityType(unusedEntityType); } @@ -766,9 +827,9 @@ private void RemoveNavigation( if (existingNavigation.ForeignKey.DeclaringEntityType.Builder .HasNoRelationship(existingNavigation.ForeignKey) == null - && existingNavigation.ForeignKey.Builder.HasNavigation( - (string?)null, existingNavigation.IsOnDependent) - == null) + && ((existingNavigation.ForeignKey.IsOwnership + && !existingNavigation.IsOnDependent) + || existingNavigation.ForeignKey.Builder.HasNavigation((string?)null, existingNavigation.IsOnDependent) == null)) { // Navigations of higher configuration source are not ambiguous toRemoveFrom.Remove(navigationProperty); @@ -1056,11 +1117,11 @@ public virtual void ProcessForeignKeyOwnershipChanged( IConventionContext context) => DiscoverRelationships(relationshipBuilder.Metadata.DeclaringEntityType.Builder, context); - private static bool IsImplicitlyCreatedUnusedSharedType(IConventionEntityType entityType) - => entityType.HasSharedClrType - && entityType.GetConfigurationSource() == ConfigurationSource.Convention - && !entityType.GetForeignKeys().Any() - && !entityType.GetReferencingForeignKeys().Any(); + private static bool IsImplicitlyCreatedUnusedType(IConventionEntityType entityType) + => (entityType.IsOwned() || entityType.HasSharedClrType) + && entityType.GetConfigurationSource() == ConfigurationSource.Convention + && !entityType.GetForeignKeys().Any() + && !entityType.GetReferencingForeignKeys().Any(); private static bool IsAmbiguous(IConventionEntityType? entityType, MemberInfo navigationProperty) { diff --git a/src/EFCore/Metadata/IConventionModel.cs b/src/EFCore/Metadata/IConventionModel.cs index 22df45e2884..38f3862f2b9 100644 --- a/src/EFCore/Metadata/IConventionModel.cs +++ b/src/EFCore/Metadata/IConventionModel.cs @@ -293,7 +293,7 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable new IEnumerable FindLeastDerivedEntityTypes( Type type, Func? condition = null) - => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition == null ? null : t => condition(t)) + => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition) .Cast(); /// @@ -304,6 +304,24 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable /// Indicates whether the configuration was specified using a data annotation. void AddShared(Type type, bool fromDataAnnotation = false); + /// + /// Marks the given type as not shared, indicating that when discovered matching entity types + /// should not be configured as shared type entity types. + /// + /// The type of the entity type that should be shared. + /// The removed type. + Type? RemoveShared(Type type); + + /// + /// Returns the configuration source if the given type is marked as shared. + /// + /// The type that could be shared. + /// + /// The configuration source if the given type is marked as shared, + /// otherwise. + /// + ConfigurationSource? FindIsSharedConfigurationSource(Type type); + /// /// Marks the given entity type as owned, indicating that when discovered entity types using the given type /// should be configured as owned. @@ -326,18 +344,17 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable /// /// The type of the entity type that could be owned. /// - /// if the given type name is marked as owned, + /// if the given type is marked as owned, /// otherwise. /// bool IsOwned(Type type) => FindIsOwnedConfigurationSource(type) != null; /// - /// Returns a value indicating whether the entity types using the given type should be configured - /// as owned types when discovered. + /// Returns the configuration source if the given type is marked as owned. /// /// The type of the entity type that could be owned. /// - /// The configuration source if the given type name is marked as owned, + /// The configuration source if the given type is marked as owned, /// otherwise. /// ConfigurationSource? FindIsOwnedConfigurationSource(Type type); diff --git a/src/EFCore/Metadata/IModel.cs b/src/EFCore/Metadata/IModel.cs index ab5eaced5d0..f89f9b63746 100644 --- a/src/EFCore/Metadata/IModel.cs +++ b/src/EFCore/Metadata/IModel.cs @@ -141,7 +141,7 @@ RuntimeModelDependencies GetModelDependencies() new IEnumerable FindLeastDerivedEntityTypes( Type type, Func? condition = null) - => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition == null ? null : t => condition(t)) + => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition) .Cast(); /// diff --git a/src/EFCore/Metadata/IMutableModel.cs b/src/EFCore/Metadata/IMutableModel.cs index fde9f2b1c14..eee4d02234f 100644 --- a/src/EFCore/Metadata/IMutableModel.cs +++ b/src/EFCore/Metadata/IMutableModel.cs @@ -263,7 +263,7 @@ IMutableEntityType AddEntityType( new IEnumerable FindLeastDerivedEntityTypes( Type type, Func? condition = null) - => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition == null ? null : t => condition(t)) + => ((IReadOnlyModel)this).FindLeastDerivedEntityTypes(type, condition) .Cast(); /// @@ -273,6 +273,14 @@ IMutableEntityType AddEntityType( /// The type of the entity type that should be shared. void AddShared(Type type); + /// + /// Marks the given type as not shared, indicating that when discovered matching entity types + /// should not be configured as shared type entity types. + /// + /// The type of the entity type that should be shared. + /// The removed type. + Type? RemoveShared(Type type); + /// /// Marks the given entity type as owned, indicating that when discovered matching entity types /// should be configured as owned. diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index a2408591b94..6f3f6b173da 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -218,6 +218,15 @@ public virtual bool IsOwned() public virtual void SetIsOwned(bool value) => _isOwned = value; + /// + /// 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. + /// + public virtual EntityType? Owner + => FindOwnership()?.PrincipalEntityType; + /// /// 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 diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 3a6b38cb7e3..913d7ac043b 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -1151,12 +1151,19 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou Check.DebugAssert(navigation.DeclaringEntityType == Metadata, "navigation.DeclaringEntityType != Metadata"); var navigationConfigurationSource = navigation.GetConfigurationSource(); - if (foreignKey.GetConfigurationSource() != navigationConfigurationSource) + if (foreignKey.GetConfigurationSource() != navigationConfigurationSource + && (navigation.IsOnDependent + || !foreignKey.IsOwnership)) { var removedNavigation = foreignKey.Builder.HasNavigation( (MemberInfo?)null, navigation.IsOnDependent, configurationSource); Check.DebugAssert(removedNavigation != null, "removedNavigation is null"); } + else if (foreignKey.IsOwnership + && configurationSource.Overrides(foreignKey.DeclaringEntityType.GetConfigurationSource())) + { + Metadata.Model.Builder.HasNoEntityType(foreignKey.DeclaringEntityType, configurationSource); + } else { var removedForeignKey = foreignKey.DeclaringEntityType.Builder.HasNoRelationship( @@ -1223,7 +1230,9 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou if (derivedNavigation != null) { var foreignKey = derivedNavigation.ForeignKey; - if (foreignKey.GetConfigurationSource() != derivedNavigation.GetConfigurationSource()) + if (foreignKey.GetConfigurationSource() != derivedNavigation.GetConfigurationSource() + && (derivedNavigation.IsOnDependent + || !foreignKey.IsOwnership)) { if (derivedNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) { @@ -1231,6 +1240,11 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou (MemberInfo?)null, derivedNavigation.IsOnDependent, configurationSource); } } + else if (foreignKey.IsOwnership + && configurationSource.Overrides(foreignKey.DeclaringEntityType.GetConfigurationSource())) + { + Metadata.Model.Builder.HasNoEntityType(foreignKey.DeclaringEntityType, configurationSource); + } else if (foreignKey.GetConfigurationSource() != ConfigurationSource.Explicit) { foreignKey.DeclaringEntityType.Builder.HasNoRelationship( @@ -3712,13 +3726,6 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri existingTargetType.Name, existingTargetType.ClrType, configurationSource.Value, targetShouldBeOwned) : ModelBuilder.Entity(existingTargetType.ClrType, configurationSource.Value, targetShouldBeOwned); } - - if (configurationSource == null - || existingNavigation.ForeignKey.DeclaringEntityType.Builder - .HasNoRelationship(existingNavigation.ForeignKey, configurationSource.Value) == null) - { - return null; - } } if (navigation.MemberInfo == null diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index aecac93af39..f6cc6ae7873 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -390,8 +390,6 @@ public InternalForeignKeyBuilder( using var batch = Metadata.DeclaringEntityType.Model.DelayConventions(); builder = this; - IsUnique(shouldBeUnique, shouldBeUnique.HasValue ? configurationSource : ConfigurationSource.Convention); - if (navigationToPrincipal != null) { if (navigationToPrincipal.Value.Name == Metadata.PrincipalToDependent?.Name) @@ -424,6 +422,9 @@ public InternalForeignKeyBuilder( if (navigationToDependent != null) { + // TODO: Use layering instead, issue #15898 + IsUnique(shouldBeUnique, shouldBeUnique.HasValue ? configurationSource : ConfigurationSource.Convention); + var navigationProperty = navigationToDependent.Value.MemberInfo; if (navigationToDependentName != null) { @@ -1099,7 +1100,7 @@ public virtual bool CanSetIsRequiredDependent(bool? required, ConfigurationSourc } Metadata.SetIsOwnership(ownership: true, configurationSource); - newRelationshipBuilder = newRelationshipBuilder?.OnDelete(DeleteBehavior.Cascade, ConfigurationSource.Convention); + newRelationshipBuilder = newRelationshipBuilder.OnDelete(DeleteBehavior.Cascade, ConfigurationSource.Convention); if (newRelationshipBuilder == null) { @@ -1186,7 +1187,14 @@ public virtual bool CanSetIsRequiredDependent(bool? required, ConfigurationSourc foreach (var invertedOwnership in invertedOwnerships) { - invertedOwnership.DeclaringEntityType.Builder.HasNoRelationship(invertedOwnership, configurationSource); + if (configurationSource.Overrides(invertedOwnership.DeclaringEntityType.GetConfigurationSource())) + { + ModelBuilder.HasNoEntityType(invertedOwnership.DeclaringEntityType, configurationSource); + } + else + { + invertedOwnership.DeclaringEntityType.Builder.HasNoRelationship(invertedOwnership, configurationSource); + } } return batch.Run(newRelationshipBuilder); diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 4e08fb45aae..3e086d48149 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -135,11 +135,22 @@ public override InternalModelBuilder ModelBuilder else { clrType = type.Type!; - if (Metadata.IsShared(clrType)) + var sharedConfigurationSource = Metadata.FindIsSharedConfigurationSource(clrType); + if (sharedConfigurationSource != null) { - return configurationSource == ConfigurationSource.Explicit - ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.ShortDisplayName())) - : null; + if (!configurationSource.OverridesStrictly(sharedConfigurationSource.Value)) + { + return configurationSource == ConfigurationSource.Explicit + ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.ShortDisplayName())) + : null; + } + + foreach (var sharedTypeEntityType in Metadata.FindEntityTypes(clrType).ToList()) + { + HasNoEntityType(sharedTypeEntityType, configurationSource); + } + + Metadata.RemoveShared(clrType); } entityType = Metadata.FindEntityType(clrType); @@ -165,10 +176,9 @@ public override InternalModelBuilder ModelBuilder if (type.Type == null || entityType.ClrType == type.Type) { - if (shouldBeOwned.HasValue - && entityType.Builder.IsOwned(shouldBeOwned.Value, configurationSource) == null) + if (shouldBeOwned.HasValue) { - return null; + entityType.Builder.IsOwned(shouldBeOwned.Value, configurationSource); } entityType.UpdateConfigurationSource(configurationSource); @@ -362,7 +372,7 @@ private bool IsOwned(in TypeIdentity type) /// 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. /// - public virtual bool IsIgnored(Type type, ConfigurationSource configurationSource) + public virtual bool IsIgnored(Type type, ConfigurationSource? configurationSource) => IsIgnored(new TypeIdentity(type, Metadata), configurationSource); /// @@ -371,10 +381,10 @@ public virtual bool IsIgnored(Type type, ConfigurationSource configurationSource /// 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. /// - public virtual bool IsIgnored(string name, ConfigurationSource configurationSource) + public virtual bool IsIgnored(string name, ConfigurationSource? configurationSource) => IsIgnored(new TypeIdentity(name), configurationSource); - private bool IsIgnored(in TypeIdentity type, ConfigurationSource configurationSource) + private bool IsIgnored(in TypeIdentity type, ConfigurationSource? configurationSource) { if (configurationSource == ConfigurationSource.Explicit) { @@ -544,11 +554,18 @@ private bool CanIgnore(in TypeIdentity type, ConfigurationSource configurationSo using (Metadata.DelayConventions()) { - var entityTypeBuilder = entityType.Builder; foreach (var foreignKey in entityType.GetDeclaredReferencingForeignKeys().ToList()) { - var removed = foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, configurationSource); - Check.DebugAssert(removed != null, "removed is null"); + if (foreignKey.IsOwnership + && configurationSource.Overrides(foreignKey.DeclaringEntityType.GetConfigurationSource())) + { + HasNoEntityType(foreignKey.DeclaringEntityType, configurationSource); + } + else + { + var removed = foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } } foreach (var skipNavigation in entityType.GetDeclaredReferencingSkipNavigations().ToList()) diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 24ec38b34ba..12c8f1ff164 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -512,7 +512,7 @@ public virtual IReadOnlyCollection GetEntityTypes(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsShared(Type type) - => _sharedTypes.ContainsKey(type) + => FindIsSharedConfigurationSource(type) != null || Configuration?.GetConfigurationType(type) == TypeConfigurationType.SharedTypeEntityType; /// @@ -722,10 +722,30 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) return null; } - var name = GetDisplayName(type); - return _ownedTypes.Remove(name) ? name : null; + var currentType = type; + while (currentType != null) + { + var name = GetDisplayName(type); + if (_ownedTypes.Remove(name)) + { + return name; + } + + currentType = currentType.BaseType; + } + + return null; } + /// + /// 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. + /// + public virtual ConfigurationSource? FindIsSharedConfigurationSource(Type type) + => _sharedTypes.TryGetValue(type, out var existingTypes) ? existingTypes.ConfigurationSource : null; + /// /// 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 @@ -736,7 +756,7 @@ public virtual void AddShared(Type type, ConfigurationSource configurationSource { EnsureMutable(); - if (_entityTypes.Any(et => !et.Value.HasSharedClrType && et.Value.ClrType == type)) + if (FindEntityType(type) != null) { throw new InvalidOperationException(CoreStrings.CannotMarkShared(type.ShortDisplayName())); } @@ -751,6 +771,25 @@ public virtual void AddShared(Type type, ConfigurationSource configurationSource } } + /// + /// 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. + /// + public virtual Type? RemoveShared(Type type) + { + EnsureMutable(); + + if (_sharedTypes.TryGetValue(type, out var existingTypes) + && existingTypes.Types.Any()) + { + throw new InvalidOperationException(CoreStrings.CannotMarkNonShared(type.ShortDisplayName())); + } + + return _sharedTypes.Remove(type) ? type : null; + } + /// /// 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 diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 3c0b4ae9caa..8762199149f 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -271,6 +271,14 @@ public static string CannotLoadDetached(object? navigation, object? entityType) GetString("CannotLoadDetached", "0_navigation", "1_entityType"), navigation, entityType); + /// + /// The type '{type}' cannot be marked as a non-shared type since a shared type entity type with this CLR type exists in the model. + /// + public static string CannotMarkNonShared(object? type) + => string.Format( + GetString("CannotMarkNonShared", nameof(type)), + type); + /// /// The type '{type}' cannot be marked as a shared type since an entity type with the same CLR type already exists in the model. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 242ba9aacb1..aa4bbbd191f 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -211,6 +211,9 @@ The navigation '{1_entityType}.{0_navigation}' cannot be loaded because the entity is not being tracked. Navigations can only be loaded for tracked entities. + + The type '{type}' cannot be marked as a non-shared type since a shared type entity type with this CLR type exists in the model. + The type '{type}' cannot be marked as a shared type since an entity type with the same CLR type already exists in the model. diff --git a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs index 790d7a7868d..810e0fb0b4e 100644 --- a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs +++ b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs @@ -137,6 +137,7 @@ public virtual void Detects_non_key_partition_key_property() public virtual void Detects_missing_partition_key_property() { var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); modelBuilder.Entity().HasPartitionKey("PartitionKey"); VerifyError(CosmosStrings.PartitionKeyMissingProperty(typeof(Order).Name, "PartitionKey"), modelBuilder); @@ -193,6 +194,7 @@ public virtual void Detects_partition_key_of_different_type() public virtual void Detects_properties_mapped_to_same_property() { var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); modelBuilder.Entity( ob => { @@ -209,6 +211,7 @@ public virtual void Detects_properties_mapped_to_same_property() public virtual void Detects_property_and_embedded_type_mapped_to_same_property() { var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity(); modelBuilder.Entity( ob => { diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index 3cf86eb58c3..2e88f719985 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -309,6 +309,45 @@ protected override TestModelBuilder CreateModelBuilder(Action( + e => + { + e.Property(p => p.Id); + e.Property(p => p.AlternateKey); + e.Property(p => p.Description); + e.HasKey(p => p.Id); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(OneToOneOwnerWithField)); + Assert.Equal(typeof(OneToOneOwnerWithField).FullName, owner.Name); + var ownership = owner.FindNavigation(nameof(OneToOneOwnerWithField.OwnedDependent)).ForeignKey; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(OneToOneOwnerWithField.OwnedDependent), ownership.PrincipalToDependent.Name); + Assert.Equal(nameof(OneToOneOwnedWithField.OneToOneOwner), ownership.DependentToPrincipal.Name); + Assert.Equal(nameof(OneToOneOwnerWithField.Id), ownership.PrincipalKey.Properties.Single().Name); + var owned = ownership.DeclaringEntityType; + Assert.Single(owned.GetForeignKeys()); + Assert.NotNull(model.FindEntityType(typeof(OneToOneOwnedWithField))); + Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(OneToOneOwnedWithField))); + } + protected override TestModelBuilder CreateModelBuilder(Action configure = null) => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); } diff --git a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs index 5f66a647e13..ecad3a479c4 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs @@ -333,7 +333,7 @@ public void Can_mark_type_as_owned_type() Assert.NotNull( modelBuilder.Entity(typeof(Product), ConfigurationSource.Explicit) - .HasOwnership(typeof(Details), nameof(Product.Details), ConfigurationSource.Convention)); + .HasOwnership(typeof(Details), nameof(Product.Details), ConfigurationSource.Explicit)); Assert.Null(modelBuilder.Ignore(typeof(Details), ConfigurationSource.Convention)); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index 4d2559bfdca..87eb4f44722 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -20,6 +20,7 @@ public virtual void Can_configure_owned_type() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity() .OwnsOne( c => c.Details, db => @@ -52,6 +53,7 @@ public virtual void Can_configure_owned_type_using_nested_closure() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne( c => c.Details, r => r.HasAnnotation("foo", "bar") @@ -72,6 +74,7 @@ public virtual void Can_configure_one_to_one_owned_type_with_fields() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Owned(); modelBuilder.Entity( e => @@ -165,6 +168,7 @@ public virtual void Can_configure_owned_type_inverse() var modelBuilder = CreateModelBuilder(); IReadOnlyModel model = modelBuilder.Model; + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details); var owner = model.FindEntityType(typeof(Customer)); @@ -189,6 +193,7 @@ public virtual void Can_configure_owned_type_properties() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details) .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction) .HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications) @@ -210,6 +215,7 @@ public virtual void Can_configure_owned_type_key() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details) .HasKey(c => c.Id); @@ -225,6 +231,7 @@ public virtual void Can_configure_ownership_foreign_key() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity() .OwnsOne(c => c.Details) .WithOwner(d => d.Customer) @@ -243,6 +250,7 @@ public virtual void Can_configure_another_relationship_to_owner() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne( c => c.Details, r => @@ -298,6 +306,7 @@ public virtual void Can_configure_multiple_ownerships() var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details); modelBuilder.Entity().OwnsOne(c => c.Details); @@ -319,14 +328,13 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type() var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity(); modelBuilder.Entity().OwnsOne(c => c.Details) .HasOne() .WithOne() .HasPrincipalKey(); - Assert.NotNull(modelBuilder.Model.FindEntityType(typeof(CustomerDetails))); - modelBuilder.Entity().OwnsOne(c => c.Details); var model = modelBuilder.FinalizeModel(); @@ -338,7 +346,7 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type() && fk.PrincipalToDependent == null); Assert.Same(ownership.DeclaringEntityType, foreignKey.DeclaringEntityType); Assert.NotEqual(ownership.Properties.Single().Name, foreignKey.Properties.Single().Name); - Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(CustomerDetails))); + Assert.Equal(2, model.FindEntityTypes(typeof(CustomerDetails)).Count()); Assert.Equal(2, ownership.DeclaringEntityType.GetForeignKeys().Count()); } @@ -349,6 +357,7 @@ public virtual void Can_configure_owned_type_collection_from_an_owned_type() IReadOnlyModel model = modelBuilder.Model; modelBuilder.Ignore(); + modelBuilder.Ignore(); var entityBuilder = modelBuilder.Entity().OwnsOne(o => o.Customer) .OwnsMany(c => c.Orders); @@ -387,6 +396,7 @@ public virtual void Can_configure_owned_type_collection() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); var entityBuilder = modelBuilder.Entity().OwnsMany(c => c.Orders) .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction) .HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications) @@ -428,6 +438,7 @@ public virtual void Can_configure_owned_type_collection_using_nested_closure() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsMany( c => c.Orders, r => @@ -467,6 +478,7 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type_col modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsMany( c => c.Orders, ob => { @@ -476,8 +488,6 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type_col .HasPrincipalKey(); }); - Assert.NotNull(modelBuilder.Model.FindEntityType(typeof(Order))); - modelBuilder.Entity().OwnsMany(c => c.Orders) .HasKey(o => o.OrderId); @@ -501,7 +511,6 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type_col Assert.Same(ownership1.DeclaringEntityType, foreignKey.DeclaringEntityType); Assert.Null(foreignKey.PrincipalToDependent); Assert.NotEqual(ownership1.Properties.Single().Name, foreignKey.Properties.Single().Name); - Assert.Equal(5, model.GetEntityTypes().Count()); Assert.Equal(2, model.FindEntityTypes(typeof(Order)).Count()); Assert.Equal(2, ownership1.DeclaringEntityType.GetForeignKeys().Count()); @@ -543,6 +552,7 @@ public virtual void Can_configure_owned_type_from_an_owned_type_collection(HasDa var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsMany( c => c.Orders, ob => { @@ -744,7 +754,7 @@ public virtual void Ambiguous_relationship_between_owned_types_throws() Assert.Equal( CoreStrings.AmbiguousOwnedNavigation( - "Book.AlternateLabel#BookLabel.Book", + "Book.Label#BookLabel.Book", nameof(Book)), Assert.Throws(() => modelBuilder.FinalizeModel()).Message); } @@ -807,6 +817,7 @@ public virtual void Can_configure_owned_type_collection_with_one_call_afterwards { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Owned(); modelBuilder.Entity(); @@ -838,11 +849,9 @@ public virtual void Can_configure_owned_type_collection_with_one_call_afterwards Assert.Equal( nameof(SpecialOrder.SpecialOrderId), specialOwnership.DeclaringEntityType.FindPrimaryKey().Properties.Single().Name); - Assert.Equal(9, modelBuilder.Model.GetEntityTypes().Count()); Assert.Equal(2, modelBuilder.Model.FindEntityTypes(typeof(Order)).Count()); // SpecialOrder and Address are only used once, but once they are made shared they don't revert to non-shared Assert.Equal(5, modelBuilder.Model.GetEntityTypes().Count(e => !e.HasSharedClrType)); - Assert.Equal(5, modelBuilder.Model.GetEntityTypes().Count(e => e.IsOwned())); var conventionModel = (IConventionModel)modelBuilder.Model; Assert.Null(conventionModel.FindIgnoredConfigurationSource(typeof(Order))); @@ -857,6 +866,9 @@ public virtual void Can_configure_single_owned_type_using_attribute() var modelBuilder = CreateModelBuilder(); modelBuilder.Entity(); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); @@ -953,6 +965,7 @@ public virtual void Can_map_derived_of_owned_type() var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details); modelBuilder.Entity(); @@ -983,10 +996,13 @@ public virtual void Can_map_derived_of_owned_type_first() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Entity().OwnsOne(c => c.Details); IReadOnlyModel model = modelBuilder.Model; + modelBuilder.Entity(); + var owner = model.FindEntityType(typeof(OrderCombination)); var owned = owner.FindNavigation(nameof(OrderCombination.Details)).ForeignKey.DeclaringEntityType; Assert.Empty(owned.GetDirectlyDerivedTypes()); @@ -998,7 +1014,7 @@ public virtual void Can_map_derived_of_owned_type_first() return targetType != typeof(DetailsBase) && typeof(DetailsBase).IsAssignableFrom(targetType); })); Assert.Single(owned.GetForeignKeys()); - Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(DetailsBase))); + Assert.Single(model.FindEntityTypes(typeof(DetailsBase))); Assert.Null(model.FindEntityType(typeof(CustomerDetails)).BaseType); modelBuilder.Entity().Ignore(c => c.Details); @@ -1012,8 +1028,11 @@ public virtual void Throws_on_FK_matching_two_relationships() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Ignore(); modelBuilder.Entity(); + modelBuilder.Entity(); Assert.Equal( CoreStrings.AmbiguousForeignKeyPropertyCandidates( @@ -1067,6 +1086,8 @@ public virtual void Can_configure_chained_ownerships() }); }); + modelBuilder.Entity(); + var model = modelBuilder.FinalizeModel(); VerifyOwnedBookLabelModel(model); @@ -1123,6 +1144,8 @@ public virtual void Can_configure_chained_ownerships_different_order() }); }); + modelBuilder.Entity(); + var model = modelBuilder.FinalizeModel(); VerifyOwnedBookLabelModel(model); @@ -1171,6 +1194,8 @@ public virtual void Can_configure_hierarchy_with_reference_navigations_as_owned( .Ignore(l => l.Book); }); + modelBuilder.Entity(); + var model = modelBuilder.FinalizeModel(); VerifyOwnedBookLabelModel(model); @@ -1218,6 +1243,8 @@ public virtual void Can_configure_hierarchy_with_reference_navigations_as_owned_ .Ignore(l => l.Book); }); + modelBuilder.Entity(); + var model = modelBuilder.FinalizeModel(); VerifyOwnedBookLabelModel(model); @@ -1309,8 +1336,7 @@ public virtual void Reconfiguring_entity_type_as_owned_throws() Assert.Equal( CoreStrings.ClashingNonOwnedEntityType(nameof(CustomerDetails)), Assert.Throws( - () => - modelBuilder.Entity().OwnsOne(c => c.Details)).Message); + () => modelBuilder.Entity().OwnsOne(c => c.Details)).Message); } [ConditionalFact] @@ -1319,7 +1345,7 @@ public virtual void Reconfiguring_owned_type_as_non_owned_throws() var modelBuilder = CreateModelBuilder(); modelBuilder.Ignore(); - var entityType = modelBuilder.Entity().OwnsOne(c => c.Details).OwnedEntityType; + modelBuilder.Entity().OwnsOne(c => c.Details); Assert.Equal( CoreStrings.ClashingOwnedEntityType(nameof(CustomerDetails)),