From a2001eb92bd4490ef39977011b3034ea90902fff Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 27 Jul 2020 14:35:00 -0700 Subject: [PATCH] Use default property names for join entity type Remove implicit join entity type in a convention Configure PK for join entity type in a convention Consolidate HasNoSkipNavigation usage Fixes #21567 --- .../ConventionEntityTypeExtensions.cs | 8 +- .../Extensions/MutableEntityTypeExtensions.cs | 2 +- .../Builders/CollectionCollectionBuilder.cs | 22 ++-- .../Builders/CollectionCollectionBuilder`.cs | 6 +- .../Builders/CollectionNavigationBuilder.cs | 13 ++- .../Builders/IConventionEntityTypeBuilder.cs | 19 +++ .../ProviderConventionSetBuilder.cs | 11 +- ...tionDispatcher.ImmediateConventionScope.cs | 14 --- .../Conventions/KeyDiscoveryConvention.cs | 90 ++++++-------- ...nyToManyAssociationEntityTypeConvention.cs | 110 +++++++----------- .../RelationshipDiscoveryConvention.cs | 18 ++- src/EFCore/Metadata/Internal/EntityType.cs | 32 ++++- src/EFCore/Metadata/Internal/ForeignKey.cs | 5 + .../Internal/InternalEntityTypeBuilder.cs | 69 +++++++---- .../Internal/InternalForeignKeyBuilder.cs | 35 ++++-- .../Metadata/Internal/InternalModelBuilder.cs | 35 ++---- .../Internal/InternalSkipNavigationBuilder.cs | 56 +++++---- src/EFCore/Metadata/Internal/Model.cs | 16 --- .../Migrations/ModelSnapshotSqlServerTest.cs | 53 +++++---- ...ManyAssociationEntityTypeConventionTest.cs | 11 +- .../Metadata/Internal/EntityTypeTest.cs | 1 + .../Internal/InternalModelBuilderTest.cs | 32 ++--- .../InternalSkipNavigationBuilderTest.cs | 5 +- .../ModelBuilding/ManyToManyTestBase.cs | 72 +++++++----- 24 files changed, 376 insertions(+), 359 deletions(-) diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index fe70512a738..35f76e856d8 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -509,7 +509,13 @@ public static IConventionProperty AddProperty( public static IConventionProperty AddProperty( [NotNull] this IConventionEntityType entityType, [NotNull] string name, [NotNull] Type propertyType, bool setTypeConfigurationSource = true, bool fromDataAnnotation = false) - => entityType.AddProperty(name, propertyType, null, setTypeConfigurationSource, fromDataAnnotation); + => ((EntityType)entityType).AddProperty( + name, + propertyType, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : (ConfigurationSource?)null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// Adds a property backed by and indexer to this entity type. diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index 882412c6546..f9e974c3687 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -470,7 +470,7 @@ public static IMutableProperty AddProperty( /// The newly created property. public static IMutableProperty AddProperty( [NotNull] this IMutableEntityType entityType, [NotNull] string name, [NotNull] Type propertyType) - => entityType.AddProperty(name, propertyType, null); + => ((EntityType)entityType).AddProperty(name, propertyType, ConfigurationSource.Explicit, ConfigurationSource.Explicit); /// /// Adds a property backed up by an indexer to this entity type. diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs index 808ec443fb8..e223c19cf93 100644 --- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs +++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs @@ -118,21 +118,20 @@ public virtual EntityTypeBuilder UsingEntity( Check.NotNull(configureRight, nameof(configureRight)); Check.NotNull(configureLeft, nameof(configureLeft)); - var existingjoinEntityType = (EntityType) + var existingJoinEntityType = (EntityType) (LeftNavigation.ForeignKey?.DeclaringEntityType ?? RightNavigation.ForeignKey?.DeclaringEntityType); EntityType joinEntityType = null; - if (existingjoinEntityType != null) + if (existingJoinEntityType != null) { - if (existingjoinEntityType.ClrType == joinEntityClrType - && !existingjoinEntityType.HasSharedClrType) + if (existingJoinEntityType.ClrType == joinEntityClrType + && !existingJoinEntityType.HasSharedClrType) { - joinEntityType = existingjoinEntityType; + joinEntityType = existingJoinEntityType; } else { - ModelBuilder.RemoveJoinEntityIfCreatedImplicitly( - existingjoinEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit); + ModelBuilder.RemoveImplicitJoinEntity(existingJoinEntityType); } } @@ -183,8 +182,7 @@ public virtual EntityTypeBuilder UsingEntity( } else { - ModelBuilder.RemoveJoinEntityIfCreatedImplicitly( - existingJoinEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit); + ModelBuilder.RemoveImplicitJoinEntity(existingJoinEntityType); } } @@ -281,13 +279,13 @@ protected virtual void Using([NotNull] IMutableForeignKey rightForeignKey, [NotN var leftBuilder = ((SkipNavigation)LeftNavigation).Builder; var rightBuilder = ((SkipNavigation)RightNavigation).Builder; + leftBuilder = leftBuilder.HasInverse(rightBuilder.Metadata, ConfigurationSource.Explicit); + leftBuilder = leftBuilder.HasForeignKey((ForeignKey)leftForeignKey, ConfigurationSource.Explicit); rightBuilder = rightBuilder.HasForeignKey((ForeignKey)rightForeignKey, ConfigurationSource.Explicit); - leftBuilder = leftBuilder.HasInverse(rightBuilder.Metadata, ConfigurationSource.Explicit); - LeftNavigation = leftBuilder.Metadata; - RightNavigation = leftBuilder.Metadata.Inverse; + RightNavigation = rightBuilder.Metadata; } #region Hidden System.Object members diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs index de098c5687d..5b2c84d3b34 100644 --- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs @@ -88,8 +88,7 @@ public virtual EntityTypeBuilder UsingEntity( } else { - ModelBuilder.RemoveJoinEntityIfCreatedImplicitly( - existingJoinEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit); + ModelBuilder.RemoveImplicitJoinEntity(existingJoinEntityType); } } @@ -139,8 +138,7 @@ public virtual EntityTypeBuilder UsingEntity( } else { - ModelBuilder.RemoveJoinEntityIfCreatedImplicitly( - existingJoinEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit); + ModelBuilder.RemoveImplicitJoinEntity(existingJoinEntityType); } } diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs index 1b989d5ba2e..9268439bcc3 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs @@ -170,11 +170,14 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference) var navigationName = SkipNavigation.Name; var declaringEntityType = (EntityType)DeclaringEntityType; - declaringEntityType.Model.Builder - .RemoveJoinEntityIfCreatedImplicitly( - (EntityType)SkipNavigation.JoinEntityType, - removeSkipNavigations: true, - ConfigurationSource.Explicit); + + if (SkipNavigation.Inverse != null) + { + ((EntityType)SkipNavigation.Inverse.DeclaringEntityType).Builder.HasNoSkipNavigation( + (SkipNavigation)SkipNavigation.Inverse, ConfigurationSource.Explicit); + } + + declaringEntityType.Builder.HasNoSkipNavigation((SkipNavigation)SkipNavigation, ConfigurationSource.Explicit); Builder = declaringEntityType.Builder .HasRelationship( diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index 290021079c9..c60d30e7626 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -584,6 +584,25 @@ IConventionEntityTypeBuilder HasNoRelationship( /// if the foreign key can be removed from this entity type. bool CanRemoveRelationship([NotNull] IConventionForeignKey foreignKey, bool fromDataAnnotation = false); + /// + /// Removes a skip navigation from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the skip navigation was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder HasNoSkipNavigation([NotNull] ISkipNavigation skipNavigation, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the skip navigation can be removed from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the skip navigation can be removed from this entity type. + bool CanRemoveSkipNavigation([NotNull] ISkipNavigation skipNavigation, bool fromDataAnnotation = false); + /// /// Returns a value indicating whether the given navigation can be added to this entity type. /// diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index 583b45fbc15..137915e67a4 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -179,11 +179,18 @@ public virtual ConventionSet CreateConventionSet() conventionSet.NavigationAddedConventions.Add(relationshipDiscoveryConvention); conventionSet.NavigationAddedConventions.Add(foreignKeyAttributeConvention); + var manyToManyJoinEntityTypeConvention = new ManyToManyJoinEntityTypeConvention(Dependencies); conventionSet.SkipNavigationAddedConventions.Add(backingFieldConvention); + conventionSet.SkipNavigationAddedConventions.Add(manyToManyJoinEntityTypeConvention); - conventionSet.NavigationRemovedConventions.Add(relationshipDiscoveryConvention); + conventionSet.SkipNavigationRemovedConventions.Add(manyToManyJoinEntityTypeConvention); + + conventionSet.SkipNavigationInverseChangedConventions.Add(manyToManyJoinEntityTypeConvention); - conventionSet.SkipNavigationAddedConventions.Add(new ManyToManyJoinEntityTypeConvention(Dependencies)); + conventionSet.SkipNavigationForeignKeyChangedConventions.Add(manyToManyJoinEntityTypeConvention); + conventionSet.SkipNavigationForeignKeyChangedConventions.Add(keyDiscoveryConvention); + + conventionSet.NavigationRemovedConventions.Add(relationshipDiscoveryConvention); conventionSet.IndexAddedConventions.Add(foreignKeyIndexConvention); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index d25d5e505e3..04490e7ed3c 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -779,13 +779,6 @@ public override IConventionForeignKey OnSkipNavigationForeignKeyChanged( _foreignKeyConventionContext.ResetState(foreignKey); foreach (var skipNavigationConvention in _conventionSet.SkipNavigationForeignKeyChangedConventions) { - if (navigationBuilder.Metadata.Builder == null - || navigationBuilder.Metadata.ForeignKey != foreignKey) - { - Check.DebugAssert(false, "Foreign key changed"); - return null; - } - skipNavigationConvention.ProcessSkipNavigationForeignKeyChanged( navigationBuilder, foreignKey, oldForeignKey, _foreignKeyConventionContext); if (_foreignKeyConventionContext.ShouldStopProcessing()) @@ -824,13 +817,6 @@ public override IConventionSkipNavigation OnSkipNavigationInverseChanged( _skipNavigationConventionContext.ResetState(inverse); foreach (var skipNavigationConvention in _conventionSet.SkipNavigationInverseChangedConventions) { - if (navigationBuilder.Metadata.Builder == null - || navigationBuilder.Metadata.Inverse != inverse) - { - Check.DebugAssert(false, "inverse changed"); - return null; - } - skipNavigationConvention.ProcessSkipNavigationInverseChanged( navigationBuilder, inverse, oldInverse, _skipNavigationConventionContext); if (_skipNavigationConventionContext.ShouldStopProcessing()) diff --git a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs index a575996d372..c5bfcc5836d 100644 --- a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs @@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions @@ -37,7 +36,8 @@ public class KeyDiscoveryConvention : IForeignKeyRemovedConvention, IForeignKeyPropertiesChangedConvention, IForeignKeyUniquenessChangedConvention, - IForeignKeyOwnershipChangedConvention + IForeignKeyOwnershipChangedConvention, + ISkipNavigationForeignKeyChangedConvention { private const string KeySuffix = "Id"; @@ -120,6 +120,16 @@ protected virtual void TryConfigurePrimaryKey([NotNull] IConventionEntityTypeBui keyProperties.Add(extraProperty); } + if (keyProperties.Count == 0) + { + var manyToManyForeignKeys = entityType.GetForeignKeys() + .Where(fk => fk.GetReferencingSkipNavigations().Any(n => n.IsCollection)).ToList(); + if (manyToManyForeignKeys.Count() == 2) + { + keyProperties.AddRange(manyToManyForeignKeys.SelectMany(fk => fk.Properties)); + } + } + ProcessKeyProperties(keyProperties, entityType); if (keyProperties.Count > 0) @@ -166,23 +176,13 @@ public static IEnumerable DiscoverKeyProperties( // ReSharper restore PossibleMultipleEnumeration } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) => TryConfigurePrimaryKey(entityTypeBuilder); - /// - /// Called after the base type of an entity type changes. - /// - /// The builder for the entity type. - /// The new base entity type. - /// The old base entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeBaseTypeChanged( IConventionEntityTypeBuilder entityTypeBuilder, IConventionEntityType newBaseType, @@ -197,23 +197,14 @@ public virtual void ProcessEntityTypeBaseTypeChanged( TryConfigurePrimaryKey(entityTypeBuilder); } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) { TryConfigurePrimaryKey(propertyBuilder.Metadata.DeclaringEntityType.Builder); } - /// - /// Called after a key is removed. - /// - /// The builder for the entity type. - /// The removed key. - /// Additional information associated with convention execution. + /// public virtual void ProcessKeyRemoved( IConventionEntityTypeBuilder entityTypeBuilder, IConventionKey key, IConventionContext context) { @@ -223,11 +214,7 @@ public virtual void ProcessKeyRemoved( } } - /// - /// Called after a foreign key is added to the entity type. - /// - /// The builder for the foreign key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyAdded( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) @@ -238,13 +225,7 @@ public virtual void ProcessForeignKeyAdded( } } - /// - /// Called after the foreign key properties or principal key are changed. - /// - /// The builder for the foreign key. - /// The old foreign key properties. - /// The old principal key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyPropertiesChanged( IConventionForeignKeyBuilder relationshipBuilder, IReadOnlyList oldDependentProperties, @@ -260,11 +241,8 @@ public virtual void ProcessForeignKeyPropertiesChanged( } } - /// - /// Called after the ownership value for a foreign key is changed. - /// - /// The builder for the foreign key. - /// Additional information associated with convention execution. + + /// public virtual void ProcessForeignKeyOwnershipChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) @@ -272,12 +250,7 @@ public virtual void ProcessForeignKeyOwnershipChanged( TryConfigurePrimaryKey(relationshipBuilder.Metadata.DeclaringEntityType.Builder); } - /// - /// Called after a foreign key is removed. - /// - /// The builder for the entity type. - /// The removed foreign key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyRemoved( IConventionEntityTypeBuilder entityTypeBuilder, IConventionForeignKey foreignKey, @@ -289,11 +262,7 @@ public virtual void ProcessForeignKeyRemoved( } } - /// - /// Called after the uniqueness for a foreign key is changed. - /// - /// The builder for the foreign key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyUniquenessChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) @@ -303,5 +272,20 @@ public virtual void ProcessForeignKeyUniquenessChanged( TryConfigurePrimaryKey(relationshipBuilder.Metadata.DeclaringEntityType.Builder); } } + + /// + public virtual void ProcessSkipNavigationForeignKeyChanged( + IConventionSkipNavigationBuilder skipNavigationBuilder, + IConventionForeignKey foreignKey, + IConventionForeignKey oldForeignKey, + IConventionContext context) + { + var joinEntityTypeBuilder = skipNavigationBuilder.Metadata.ForeignKey?.DeclaringEntityType.Builder; + if (joinEntityTypeBuilder != null + && skipNavigationBuilder.Metadata.IsCollection) + { + TryConfigurePrimaryKey(joinEntityTypeBuilder); + } + } } } diff --git a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs index a7f614616b9..b41c99c961c 100644 --- a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs @@ -17,10 +17,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// a many-to-many join entity with suitable foreign keys, sets the two /// matching skip navigations to use those foreign keys. /// - public class ManyToManyJoinEntityTypeConvention : ISkipNavigationAddedConvention, ISkipNavigationInverseChangedConvention + public class ManyToManyJoinEntityTypeConvention : + ISkipNavigationAddedConvention, + ISkipNavigationInverseChangedConvention, + ISkipNavigationForeignKeyChangedConvention, + ISkipNavigationRemovedConvention { private const string JoinEntityTypeNameTemplate = "{0}{1}"; - private const string JoinPropertyNameTemplate = "{0}_{1}"; /// /// Creates a new instance of . @@ -41,9 +44,6 @@ public virtual void ProcessSkipNavigationAdded( IConventionSkipNavigationBuilder skipNavigationBuilder, IConventionContext context) { - Check.NotNull(skipNavigationBuilder, nameof(skipNavigationBuilder)); - Check.NotNull(context, nameof(context)); - CreateJoinEntityType(skipNavigationBuilder); } @@ -54,28 +54,46 @@ public virtual void ProcessSkipNavigationInverseChanged( IConventionSkipNavigation oldInverse, IConventionContext context) { - Check.NotNull(skipNavigationBuilder, nameof(skipNavigationBuilder)); - Check.NotNull(context, nameof(context)); - CreateJoinEntityType(skipNavigationBuilder); } - private void CreateJoinEntityType( - IConventionSkipNavigationBuilder skipNavigationBuilder) + /// + public virtual void ProcessSkipNavigationForeignKeyChanged( + IConventionSkipNavigationBuilder skipNavigationBuilder, + IConventionForeignKey foreignKey, + IConventionForeignKey oldForeignKey, + IConventionContext context) { - var skipNavigation = (SkipNavigation)skipNavigationBuilder.Metadata; - if (skipNavigation.JoinEntityType != null) + var joinEntityType = oldForeignKey?.DeclaringEntityType; + var navigation = skipNavigationBuilder.Metadata; + if (joinEntityType?.Builder != null + && navigation.IsCollection + && navigation.ForeignKey?.DeclaringEntityType != joinEntityType) { - return; + ((InternalModelBuilder)joinEntityType.Model.Builder).RemoveImplicitJoinEntity((EntityType)joinEntityType); + } + } + + /// + public virtual void ProcessSkipNavigationRemoved( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionSkipNavigation navigation, + IConventionContext context) + { + var joinEntityType = navigation.ForeignKey?.DeclaringEntityType; + if (joinEntityType?.Builder != null + && navigation.IsCollection) + { + ((InternalModelBuilder)joinEntityType.Model.Builder).RemoveImplicitJoinEntity((EntityType)joinEntityType); } + } + private void CreateJoinEntityType(IConventionSkipNavigationBuilder skipNavigationBuilder) + { + var skipNavigation = (SkipNavigation)skipNavigationBuilder.Metadata; if (skipNavigation.ForeignKey != null - || skipNavigation.TargetEntityType == skipNavigation.DeclaringEntityType || !skipNavigation.IsCollection) { - // do not create the join entity type for a self-referencing - // skip navigation, or for one that is already "in use" - // (i.e. has its Foreign Key assigned). return; } @@ -84,9 +102,6 @@ private void CreateJoinEntityType( || inverseSkipNavigation.ForeignKey != null || !inverseSkipNavigation.IsCollection) { - // do not create the join entity type if - // the inverse skip navigation is already "in use" - // (i.e. has its Foreign Key assigned). return; } @@ -97,7 +112,6 @@ private void CreateJoinEntityType( var inverseEntityType = inverseSkipNavigation.DeclaringEntityType; var model = declaringEntityType.Model; - // create the join entity type var joinEntityTypeName = string.Format( JoinEntityTypeNameTemplate, declaringEntityType.ShortName(), @@ -114,74 +128,32 @@ private void CreateJoinEntityType( var joinEntityTypeBuilder = model.Builder.SharedTypeEntity( joinEntityTypeName, Model.DefaultPropertyBagType, ConfigurationSource.Convention); - // Create left and right foreign keys from the outer entity types to - // the join entity type and configure the skip navigations. - // Roll back if any of this fails. - var leftForeignKey = - CreateSkipNavigationForeignKey(skipNavigation, joinEntityTypeBuilder); + var leftForeignKey = CreateSkipNavigationForeignKey(skipNavigation, joinEntityTypeBuilder); if (leftForeignKey == null) { - model.Builder.HasNoEntityType( - joinEntityTypeBuilder.Metadata, ConfigurationSource.Convention); + model.Builder.HasNoEntityType(joinEntityTypeBuilder.Metadata, ConfigurationSource.Convention); return; } - var rightForeignKey = - CreateSkipNavigationForeignKey(inverseSkipNavigation, joinEntityTypeBuilder); + var rightForeignKey = CreateSkipNavigationForeignKey(inverseSkipNavigation, joinEntityTypeBuilder); if (rightForeignKey == null) { - // Removing the join entity type will also remove - // the leftForeignKey created above. - model.Builder.HasNoEntityType( - joinEntityTypeBuilder.Metadata, ConfigurationSource.Convention); + model.Builder.HasNoEntityType(joinEntityTypeBuilder.Metadata, ConfigurationSource.Convention); return; } skipNavigation.Builder.HasForeignKey(leftForeignKey, ConfigurationSource.Convention); inverseSkipNavigation.Builder.HasForeignKey(rightForeignKey, ConfigurationSource.Convention); - - // Creating the primary key below also negates the need for an index on - // the properties of leftForeignKey - that index is automatically removed. - joinEntityTypeBuilder.PrimaryKey( - leftForeignKey.Properties.Concat(rightForeignKey.Properties).ToList(), - ConfigurationSource.Convention); } private static ForeignKey CreateSkipNavigationForeignKey( SkipNavigation skipNavigation, InternalEntityTypeBuilder joinEntityTypeBuilder) - { - var principalEntityType = skipNavigation.DeclaringEntityType; - var principalKey = principalEntityType.FindPrimaryKey(); - if (principalKey == null) - { - return null; - } - - var dependentEndForeignKeyPropertyNames = new List(); - var otherIdentifiers = joinEntityTypeBuilder.Metadata - .GetDeclaredProperties().ToDictionary(p => p.Name, p => 0); - foreach (var property in principalKey.Properties) - { - var propertyName = Uniquifier.Uniquify( - string.Format( - JoinPropertyNameTemplate, - principalEntityType.ShortName(), - property.Name), - otherIdentifiers, - int.MaxValue); - dependentEndForeignKeyPropertyNames.Add(propertyName); - otherIdentifiers.Add(propertyName, 0); - } - - return joinEntityTypeBuilder + => joinEntityTypeBuilder .HasRelationship( - principalEntityType.Name, - dependentEndForeignKeyPropertyNames, - principalKey, + skipNavigation.DeclaringEntityType, ConfigurationSource.Convention) .IsUnique(false, ConfigurationSource.Convention) .Metadata; - } } } diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index f8789a0fc91..ba2155aaa40 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -747,21 +747,19 @@ private void RemoveNavigation( } else { - var joinEntityType = (EntityType)declaringEntityType - .FindDeclaredSkipNavigation(navigationPropertyName)? - .ForeignKey?.DeclaringEntityType; - if (joinEntityType != null) + var skipNavigation = declaringEntityType.FindDeclaredSkipNavigation(navigationPropertyName); + if (skipNavigation != null) { - var modelBuilder = joinEntityType.Model.Builder; - // The PropertyInfo underlying this skip navigation has become - // ambiguous since we used it, so remove the join entity - // if it was implicitly created. - if (modelBuilder.RemoveJoinEntityIfCreatedImplicitly( - joinEntityType, removeSkipNavigations: true, ConfigurationSource.Convention) == null) + var inverse = skipNavigation.Inverse; + if (declaringEntityType.Builder.HasNoSkipNavigation(skipNavigation) == null) { // Navigations of higher configuration source are not ambiguous toRemoveFrom.Remove(navigationProperty); } + else if (inverse?.Builder != null) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse); + } } } } diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index e09c90f46ce..804ffdea3a4 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -388,6 +388,28 @@ public virtual EntityType SetBaseType([CanBeNull] EntityType newBaseType, Config /// public virtual void OnTypeRemoved() { + if (_foreignKeys.Count > 0) + { + foreach (var foreignKey in GetDeclaredForeignKeys().ToList()) + { + if (foreignKey.PrincipalEntityType != this) + { + RemoveForeignKey(foreignKey); + } + } + } + + if (_skipNavigations.Count > 0) + { + foreach (var skipNavigation in GetDeclaredSkipNavigations().ToList()) + { + if (skipNavigation.TargetEntityType != this) + { + RemoveSkipNavigation(skipNavigation); + } + } + } + Builder = null; _baseType?._directlyDerivedTypes.Remove(this); @@ -2261,7 +2283,7 @@ public virtual Property AddProperty( return AddProperty( name, propertyType, - null, + ClrType?.GetMembersInHierarchy(name).FirstOrDefault(), typeConfigurationSource, configurationSource); } @@ -2341,7 +2363,8 @@ public virtual Property AddProperty( } else { - memberInfo = ClrType?.GetMembersInHierarchy(name).FirstOrDefault(); + Check.DebugAssert(ClrType?.GetMembersInHierarchy(name).FirstOrDefault() == null, + "MemberInfo not supplied for non-shadow property"); } if (memberInfo != null @@ -3904,7 +3927,8 @@ IConventionIndex IConventionEntityType.AddIndex( /// [DebuggerStepThrough] IMutableProperty IMutableEntityType.AddProperty(string name, Type propertyType, MemberInfo memberInfo) - => AddProperty(name, propertyType, memberInfo, ConfigurationSource.Explicit, ConfigurationSource.Explicit); + => AddProperty(name, propertyType, memberInfo ?? ClrType?.GetMembersInHierarchy(name).FirstOrDefault(), + ConfigurationSource.Explicit, ConfigurationSource.Explicit); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3918,7 +3942,7 @@ IConventionProperty IConventionEntityType.AddProperty( => AddProperty( name, propertyType, - memberInfo, + memberInfo ?? ClrType?.GetMembersInHierarchy(name).FirstOrDefault(), setTypeConfigurationSource ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention : (ConfigurationSource?)null, diff --git a/src/EFCore/Metadata/Internal/ForeignKey.cs b/src/EFCore/Metadata/Internal/ForeignKey.cs index 0ff51a8729f..a73ae776eda 100644 --- a/src/EFCore/Metadata/Internal/ForeignKey.cs +++ b/src/EFCore/Metadata/Internal/ForeignKey.cs @@ -1089,6 +1089,11 @@ IConventionNavigation IConventionForeignKey.SetPrincipalToDependent(string name, IConventionNavigation IConventionForeignKey.SetPrincipalToDependent(MemberInfo property, bool fromDataAnnotation) => HasPrincipalToDependent(property, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + [DebuggerStepThrough] + IEnumerable IForeignKey.GetReferencingSkipNavigations() + => GetReferencingSkipNavigations(); + /// /// 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 274c41da44a..b94d6b41ed9 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -659,10 +659,9 @@ private InternalPropertyBuilder Property( var inverse = conflictingSkipNavigation.Inverse; if (inverse?.Builder != null - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); } conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( @@ -881,10 +880,9 @@ public virtual InternalServicePropertyBuilder ServiceProperty( var inverse = conflictingSkipNavigation.Inverse; if (inverse?.Builder != null - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); } conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( @@ -1047,16 +1045,14 @@ public virtual InternalEntityTypeBuilder Ignore([NotNull] string name, Configura { var inverse = skipNavigation.Inverse; if (inverse?.Builder != null - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.SetInverse(null, configurationSource); - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); } Check.DebugAssert(skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); - Metadata.RemoveSkipNavigation(skipNavigation); + Metadata.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); } else { @@ -1117,18 +1113,14 @@ public virtual InternalEntityTypeBuilder Ignore([NotNull] string name, Configura { var inverse = skipNavigation.Inverse; if (inverse?.Builder != null - && configurationSource != inverse.GetConfigurationSource() - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.SetInverse(null, configurationSource); - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); } - if (configurationSource.Overrides(skipNavigation.GetConfigurationSource()) - && skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) + if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) { - derivedType.RemoveSkipNavigation(skipNavigation); + derivedType.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); } } else @@ -1467,7 +1459,7 @@ public virtual InternalEntityTypeBuilder HasBaseType( return baseNavigation != null && n.TargetEntityType == baseNavigation.TargetEntityType; }, - n => n.DeclaringEntityType.RemoveSkipNavigation(n)); + n => n.DeclaringEntityType.Builder.HasNoSkipNavigation(n, ConfigurationSource.Explicit)); if (skipNavigationsToDetach != null) { @@ -1965,6 +1957,11 @@ public virtual InternalEntityTypeBuilder HasNoRelationship( [NotNull] ForeignKey foreignKey, ConfigurationSource configurationSource) { + if (foreignKey.Builder == null) + { + return this; + } + var currentConfigurationSource = foreignKey.GetConfigurationSource(); if (!configurationSource.Overrides(currentConfigurationSource)) { @@ -1982,6 +1979,11 @@ public virtual InternalEntityTypeBuilder HasNoRelationship( } } + if (foreignKey.Builder == null) + { + return this; + } + Metadata.RemoveForeignKey(foreignKey); RemoveUnusedShadowProperties(foreignKey.Properties); @@ -3673,8 +3675,7 @@ public virtual InternalSkipNavigationBuilder HasSkipNavigation( public virtual InternalEntityTypeBuilder HasNoSkipNavigation( [NotNull] SkipNavigation skipNavigation, ConfigurationSource configurationSource) { - var currentConfigurationSource = skipNavigation.GetConfigurationSource(); - if (!configurationSource.Overrides(currentConfigurationSource)) + if (!CanRemoveSkipNavigation(skipNavigation, configurationSource)) { return null; } @@ -3690,6 +3691,15 @@ public virtual InternalEntityTypeBuilder HasNoSkipNavigation( return this; } + /// + /// 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 bool CanRemoveSkipNavigation([NotNull] SkipNavigation skipNavigation, ConfigurationSource configurationSource) + => configurationSource.Overrides(skipNavigation.GetConfigurationSource()); + private static InternalSkipNavigationBuilder DetachSkipNavigation(SkipNavigation skipNavigationToDetach) { var builder = skipNavigationToDetach?.Builder; @@ -3698,7 +3708,7 @@ private static InternalSkipNavigationBuilder DetachSkipNavigation(SkipNavigation return null; } - skipNavigationToDetach.DeclaringEntityType.RemoveSkipNavigation(skipNavigationToDetach); + skipNavigationToDetach.DeclaringEntityType.Builder.HasNoSkipNavigation(skipNavigationToDetach, ConfigurationSource.Explicit); return builder; } @@ -3884,7 +3894,6 @@ public virtual IReadOnlyList GetOrCreateProperties( return null; } - // TODO: Log that a shadow property is created var propertyBuilder = Property( required ? type @@ -4835,6 +4844,18 @@ IConventionSkipNavigationBuilder IConventionEntityTypeBuilder.HasSkipNavigation( collection, onDependent); + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder IConventionEntityTypeBuilder.HasNoSkipNavigation(ISkipNavigation skipNavigation, bool fromDataAnnotation) + => HasNoSkipNavigation((SkipNavigation)skipNavigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveSkipNavigation(ISkipNavigation skipNavigation, bool fromDataAnnotation) + => CanRemoveSkipNavigation((SkipNavigation)skipNavigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// 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/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index 6e368d54030..dbf1c94ec8a 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -314,13 +314,12 @@ private InternalForeignKeyBuilder HasNavigations( var inverse = conflictingSkipNavigation.Inverse; if (inverse?.Builder != null - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(conflictingSkipNavigation, configurationSource); } - conflictingSkipNavigation.DeclaringEntityType.RemoveSkipNavigation(conflictingSkipNavigation); + conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation(conflictingSkipNavigation, configurationSource); } } @@ -351,13 +350,12 @@ private InternalForeignKeyBuilder HasNavigations( var inverse = conflictingSkipNavigation.Inverse; if (inverse?.Builder != null - && inverse.DeclaringEntityType.Builder - .CanRemoveSkipNavigation(inverse, configurationSource)) + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.RemoveSkipNavigation(inverse); + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(conflictingSkipNavigation, configurationSource); } - conflictingSkipNavigation.DeclaringEntityType.RemoveSkipNavigation(conflictingSkipNavigation); + conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation(conflictingSkipNavigation, configurationSource); } } @@ -2191,6 +2189,9 @@ private InternalForeignKeyBuilder ReplaceForeignKey( InternalForeignKeyBuilder newRelationshipBuilder; using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) { + var referencingSkipNavigations = Metadata.ReferencingSkipNavigations + ?.Select(n => (Navigation: n, ConfigurationSource: n.GetForeignKeyConfigurationSource().Value)).ToList(); + newRelationshipBuilder = GetOrCreateRelationshipBuilder( principalEntityTypeBuilder.Metadata, dependentEntityTypeBuilder.Metadata, @@ -2574,6 +2575,24 @@ private InternalForeignKeyBuilder ReplaceForeignKey( ?? newRelationshipBuilder; } + if (referencingSkipNavigations != null) + { + foreach (var referencingNavigationTuple in referencingSkipNavigations) + { + var skipNavigation = referencingNavigationTuple.Navigation; + if (skipNavigation.Builder == null) + { + var navigationEntityType = skipNavigation.DeclaringEntityType; + skipNavigation = navigationEntityType.Builder == null + ? null + : navigationEntityType.FindSkipNavigation(skipNavigation.Name); + } + + skipNavigation?.Builder.HasForeignKey( + newRelationshipBuilder.Metadata, referencingNavigationTuple.ConfigurationSource); + } + } + if (Metadata != newRelationshipBuilder.Metadata) { newRelationshipBuilder.MergeAnnotationsFrom(Metadata); diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 788267521d1..5cd05fbd2bc 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -258,38 +258,21 @@ private InternalEntityTypeBuilder Entity( /// 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 InternalModelBuilder RemoveJoinEntityIfCreatedImplicitly( - [NotNull] EntityType joinEntityType, - bool removeSkipNavigations, - ConfigurationSource configurationSource) + public virtual InternalModelBuilder RemoveImplicitJoinEntity([NotNull] EntityType joinEntityType) { Check.NotNull(joinEntityType, nameof(joinEntityType)); - if (!joinEntityType.IsImplicitlyCreatedJoinEntityType) + if (joinEntityType.Builder == null) { - return null; + return this; } - Debug.Assert(joinEntityType.GetForeignKeys().Count() == 2, - "Implicitly created join entity types should have exactly 2 foreign keys"); - foreach (var fk in joinEntityType.GetForeignKeys()) + if (!joinEntityType.IsImplicitlyCreatedJoinEntityType) { - var skipNavigation = fk.GetReferencingSkipNavigations().FirstOrDefault(); - if (skipNavigation != null) - { - skipNavigation.SetForeignKey(null, configurationSource); - skipNavigation.SetInverse(null, configurationSource); - - if (removeSkipNavigations - && fk.PrincipalEntityType.Builder - .CanRemoveSkipNavigation(skipNavigation, configurationSource)) - { - fk.PrincipalEntityType.RemoveSkipNavigation(skipNavigation); - } - } + return null; } - return HasNoEntityType(joinEntityType, configurationSource); + return HasNoEntityType(joinEntityType, ConfigurationSource.Convention); } /// @@ -515,6 +498,12 @@ public virtual InternalModelBuilder HasNoEntityType([NotNull] EntityType entityT Check.DebugAssert(removed != null, "removed is null"); } + foreach (var skipNavigation in entityType.GetDeclaredForeignKeys().SelectMany(fk => fk.GetReferencingSkipNavigations()).ToList()) + { + var removed = skipNavigation.Builder.HasForeignKey(null, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + foreach (var directlyDerivedType in entityType.GetDirectlyDerivedTypes().ToList()) { var derivedEntityTypeBuilder = directlyDerivedType.Builder diff --git a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs index 7a2946d191a..a73b38799a4 100644 --- a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs @@ -68,18 +68,13 @@ public virtual InternalSkipNavigationBuilder HasForeignKey([CanBeNull] ForeignKe if (foreignKey != null) { foreignKey.UpdateConfigurationSource(configurationSource); - } - if (Metadata.JoinEntityType != null - && foreignKey?.DeclaringEntityType != Metadata.JoinEntityType) - { - // Have reset the foreign key of a skip navigation on one side of an - // join entity type to a different entity type. An implicit - // join entity type is only useful if both sides are - // configured - so, if it is implicit, remove that entity type - // (which will also remove the other skip navigation's foreign key). - Metadata.JoinEntityType.Model.Builder.RemoveJoinEntityIfCreatedImplicitly( - Metadata.JoinEntityType, removeSkipNavigations: false, configurationSource); + if (Metadata.Inverse?.JoinEntityType != null + && Metadata.Inverse.JoinEntityType + != (Metadata.IsOnDependent ? foreignKey.PrincipalEntityType : foreignKey.DeclaringEntityType)) + { + Metadata.Inverse.Builder.HasForeignKey(null, configurationSource); + } } Metadata.SetForeignKey(foreignKey, configurationSource); @@ -107,12 +102,20 @@ public virtual bool CanSetForeignKey([CanBeNull] ForeignKey foreignKey, Configur return true; } - return (Metadata.DeclaringEntityType - == (Metadata.IsOnDependent ? foreignKey.DeclaringEntityType : foreignKey.PrincipalEntityType)) - && (Metadata.Inverse?.JoinEntityType == null - || Metadata.Inverse.JoinEntityType.IsImplicitlyCreatedJoinEntityType == true - || Metadata.Inverse.JoinEntityType - == (Metadata.IsOnDependent ? foreignKey.PrincipalEntityType : foreignKey.DeclaringEntityType)); + if (Metadata.DeclaringEntityType + != (Metadata.IsOnDependent ? foreignKey.DeclaringEntityType : foreignKey.PrincipalEntityType)) + { + return false; + } + + if (Metadata.Inverse?.JoinEntityType == null) + { + return true; + } + + return Metadata.Inverse.JoinEntityType + == (Metadata.IsOnDependent ? foreignKey.PrincipalEntityType : foreignKey.DeclaringEntityType) + || Metadata.Inverse.Builder.CanSetForeignKey(null, configurationSource); } /// @@ -134,17 +137,20 @@ public virtual InternalSkipNavigationBuilder HasInverse( inverse.UpdateConfigurationSource(configurationSource); } - if (Metadata.Inverse != null - && Metadata.Inverse != inverse) + using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) { - Metadata.Inverse.SetInverse(null, configurationSource); - } + if (Metadata.Inverse != null + && Metadata.Inverse != inverse) + { + Metadata.Inverse.SetInverse(null, configurationSource); + } - Metadata.SetInverse(inverse, configurationSource); + Metadata.SetInverse(inverse, configurationSource); - if (inverse != null) - { - inverse.SetInverse(Metadata, configurationSource); + if (inverse != null) + { + inverse.SetInverse(Metadata, configurationSource); + } } return this; diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 4ac402bfd7d..fb0cf19b956 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -340,22 +340,6 @@ public virtual EntityType RemoveEntityType([CanBeNull] EntityType entityType) AssertCanRemove(entityType); - foreach (var foreignKey in entityType.GetDeclaredForeignKeys().ToList()) - { - if (foreignKey.PrincipalEntityType != entityType) - { - entityType.RemoveForeignKey(foreignKey); - } - } - - foreach (var skipNavigation in entityType.GetSkipNavigations().ToList()) - { - if (skipNavigation.TargetEntityType != entityType) - { - entityType.RemoveSkipNavigation(skipNavigation); - } - } - var entityTypeName = entityType.Name; if (entityType.HasDefiningNavigation()) { diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 6ff3f511caf..e3936979904 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1125,15 +1125,15 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() + @" modelBuilder.Entity(""ManyToManyLeftManyToManyRight"", b => { - b.Property(""ManyToManyLeft_Id"") + b.Property(""ManyToManyLeftId"") .HasColumnType(""int""); - b.Property(""ManyToManyRight_Id"") + b.Property(""ManyToManyRightId"") .HasColumnType(""int""); - b.HasKey(""ManyToManyLeft_Id"", ""ManyToManyRight_Id""); + b.HasKey(""ManyToManyLeftId"", ""ManyToManyRightId""); - b.HasIndex(""ManyToManyRight_Id""); + b.HasIndex(""ManyToManyRightId""); b.ToTable(""ManyToManyLeftManyToManyRight""); }); @@ -1172,16 +1172,16 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() { b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft"", null) .WithMany() - .HasForeignKey(""ManyToManyLeft_Id"") + .HasForeignKey(""ManyToManyLeftId"") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyRight"", null) .WithMany() - .HasForeignKey(""ManyToManyRight_Id"") + .HasForeignKey(""ManyToManyRightId"") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - });", usingSystem: true), + });"), model => { var joinEntity = model.FindEntityType("ManyToManyLeftManyToManyRight"); @@ -1189,22 +1189,22 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() Assert.Collection(joinEntity.GetDeclaredProperties(), p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); Assert.True(p.IsShadowProperty()); }, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); Assert.True(p.IsShadowProperty()); }); Assert.Collection(joinEntity.FindDeclaredPrimaryKey().Properties, p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); }, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); }); Assert.Collection(joinEntity.GetDeclaredForeignKeys(), fk => @@ -1218,7 +1218,7 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() Assert.Collection(fk.Properties, p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); }); }, fk => @@ -1232,13 +1232,12 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() Assert.Collection(fk.Properties, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); }); }); }); } - [ConditionalFact] public virtual void Can_override_table_name_for_many_to_many_join_table_stored_in_snapshot() { @@ -1256,15 +1255,15 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i + @" modelBuilder.Entity(""ManyToManyLeftManyToManyRight"", b => { - b.Property(""ManyToManyLeft_Id"") + b.Property(""ManyToManyLeftId"") .HasColumnType(""int""); - b.Property(""ManyToManyRight_Id"") + b.Property(""ManyToManyRightId"") .HasColumnType(""int""); - b.HasKey(""ManyToManyLeft_Id"", ""ManyToManyRight_Id""); + b.HasKey(""ManyToManyLeftId"", ""ManyToManyRightId""); - b.HasIndex(""ManyToManyRight_Id""); + b.HasIndex(""ManyToManyRightId""); b.ToTable(""MyJoinTable""); }); @@ -1303,16 +1302,16 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i { b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyLeft"", null) .WithMany() - .HasForeignKey(""ManyToManyLeft_Id"") + .HasForeignKey(""ManyToManyLeftId"") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+ManyToManyRight"", null) .WithMany() - .HasForeignKey(""ManyToManyRight_Id"") + .HasForeignKey(""ManyToManyRightId"") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - });", usingSystem: true), + });"), model => { var joinEntity = model.FindEntityType("ManyToManyLeftManyToManyRight"); @@ -1321,22 +1320,22 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i Assert.Collection(joinEntity.GetDeclaredProperties(), p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); Assert.True(p.IsShadowProperty()); }, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); Assert.True(p.IsShadowProperty()); }); Assert.Collection(joinEntity.FindDeclaredPrimaryKey().Properties, p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); }, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); }); Assert.Collection(joinEntity.GetDeclaredForeignKeys(), fk => @@ -1350,7 +1349,7 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i Assert.Collection(fk.Properties, p => { - Assert.Equal("ManyToManyLeft_Id", p.Name); + Assert.Equal("ManyToManyLeftId", p.Name); }); }, fk => @@ -1364,7 +1363,7 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i Assert.Collection(fk.Properties, p => { - Assert.Equal("ManyToManyRight_Id", p.Name); + Assert.Equal("ManyToManyRightId", p.Name); }); }); }); diff --git a/test/EFCore.Tests/Metadata/Conventions/ManyToManyAssociationEntityTypeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ManyToManyAssociationEntityTypeConventionTest.cs index e24ea598bc5..e00f75b6d1a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ManyToManyAssociationEntityTypeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ManyToManyAssociationEntityTypeConventionTest.cs @@ -26,7 +26,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions public class ManyToManyJoinEntityTypeConventionTest { [ConditionalFact] - public void Join_entity_type_is_not_created_for_self_join() + public void Join_entity_type_is_created_for_self_join() { var modelBuilder = CreateInternalModeBuilder(); var manyToManySelf = modelBuilder.Entity(typeof(ManyToManySelf), ConfigurationSource.Convention); @@ -45,7 +45,7 @@ public void Join_entity_type_is_not_created_for_self_join() RunConvention(firstSkipNav); - Assert.Empty(manyToManySelf.Metadata.Model.GetEntityTypes() + Assert.Single(manyToManySelf.Metadata.Model.GetEntityTypes() .Where(et => et.IsImplicitlyCreatedJoinEntityType)); } @@ -243,13 +243,6 @@ public void Join_entity_type_is_created() Assert.Equal(2, joinEntityType.GetForeignKeys().Count()); Assert.Equal(manyToManyFirstForeignKey.DeclaringEntityType, joinEntityType); Assert.Equal(manyToManySecondForeignKey.DeclaringEntityType, joinEntityType); - - var key = joinEntityType.FindPrimaryKey(); - Assert.Equal( - new[] { - nameof(ManyToManyFirst) + "_" + nameof(ManyToManyFirst.Id), - nameof(ManyToManySecond) + "_" + nameof(ManyToManySecond.Id) }, - key.Properties.Select(p => p.Name)); } public ListLoggerFactory ListLoggerFactory { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 2c5b1a42c92..c475ec6f873 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -65,6 +65,7 @@ private class FakeEntityType : IEntityType public IModel Model { get; } public string Name { get; } public bool HasSharedClrType { get; } + public bool IsPropertyBag { get; } public Type ClrType { get; } public IEntityType BaseType { get; } public string DefiningNavigationName { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs index bbef0222624..40defd292d5 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs @@ -348,10 +348,8 @@ public void Can_mark_type_as_owned_type() Assert.Throws(() => modelBuilder.Owned(typeof(Details), ConfigurationSource.Explicit)).Message); } - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public void Can_remove_implicitly_created_join_entity_type(bool removeSkipNavs) + [ConditionalFact] + public void Can_remove_implicitly_created_join_entity_type() { var model = new Model(); var modelBuilder = CreateModelBuilder(model); @@ -401,31 +399,20 @@ public void Can_remove_implicitly_created_join_entity_type(bool removeSkipNavs) var joinEntityType = joinEntityTypeBuilder.Metadata; Assert.NotNull(joinEntityType); - - Assert.NotNull(modelBuilder.RemoveJoinEntityIfCreatedImplicitly( - joinEntityType, removeSkipNavs, ConfigurationSource.Convention)); + Assert.NotNull(modelBuilder.RemoveImplicitJoinEntity(joinEntityType)); Assert.Empty(model.GetEntityTypes() .Where(e => e.IsImplicitlyCreatedJoinEntityType)); var leftSkipNav = manyToManyLeft.Metadata.FindDeclaredSkipNavigation(nameof(ManyToManyLeft.Rights)); var rightSkipNav = manyToManyRight.Metadata.FindDeclaredSkipNavigation(nameof(ManyToManyRight.Lefts)); - if (removeSkipNavs) - { - Assert.Null(leftSkipNav); - Assert.Null(rightSkipNav); - } - else - { - Assert.NotNull(leftSkipNav); - Assert.NotNull(rightSkipNav); - } + + Assert.NotNull(leftSkipNav); + Assert.NotNull(rightSkipNav); } - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public void Cannot_remove_manually_created_join_entity_type(bool removeSkipNavs) + [ConditionalFact] + public void Cannot_remove_manually_created_join_entity_type() { var model = new Model(); var modelBuilder = CreateModelBuilder(model); @@ -465,8 +452,7 @@ public void Cannot_remove_manually_created_join_entity_type(bool removeSkipNavs) Assert.NotNull(joinEntityType); Assert.Same(joinEntityType, skipNavOnRight.Metadata.JoinEntityType); - Assert.Null(modelBuilder.RemoveJoinEntityIfCreatedImplicitly( - joinEntityType, removeSkipNavs, ConfigurationSource.Convention)); + Assert.Null(modelBuilder.RemoveImplicitJoinEntity(joinEntityType)); var leftSkipNav = manyToManyLeft.Metadata.FindDeclaredSkipNavigation(nameof(ManyToManyLeft.Rights)); var rightSkipNav = manyToManyRight.Metadata.FindDeclaredSkipNavigation(nameof(ManyToManyRight.Lefts)); diff --git a/test/EFCore.Tests/Metadata/Internal/InternalSkipNavigationBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalSkipNavigationBuilderTest.cs index 8348c97dd0f..bd8dd6300ba 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalSkipNavigationBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalSkipNavigationBuilderTest.cs @@ -126,7 +126,6 @@ public void Can_only_override_lower_or_equal_source_ForeignKey() var builder = CreateInternalSkipNavigationBuilder(); IConventionSkipNavigation metadata = builder.Metadata; - // the skip navigation is pointing to the automatically-generated join entity type var originalFK = metadata.ForeignKey; Assert.NotNull(originalFK); Assert.Equal(ConfigurationSource.Convention, metadata.GetForeignKeyConfigurationSource()); @@ -137,17 +136,17 @@ public void Can_only_override_lower_or_equal_source_ForeignKey() .IsUnique(false) .Metadata; - // skip navigation is unaffected by the FK created above Assert.NotSame(fk, metadata.ForeignKey); Assert.Same(originalFK, metadata.ForeignKey); Assert.Equal(ConfigurationSource.Convention, metadata.GetForeignKeyConfigurationSource()); + Assert.NotNull(metadata.Inverse.ForeignKey); - // now explicitly assign the skip navigation's ForeignKey Assert.True(builder.CanSetForeignKey(fk, ConfigurationSource.DataAnnotation)); Assert.NotNull(builder.HasForeignKey(fk, ConfigurationSource.DataAnnotation)); Assert.Equal(fk, metadata.ForeignKey); Assert.Equal(ConfigurationSource.DataAnnotation, metadata.GetForeignKeyConfigurationSource()); + Assert.Null(metadata.Inverse.ForeignKey); Assert.True(builder.CanSetForeignKey(fk, ConfigurationSource.Convention)); Assert.False(builder.CanSetForeignKey(null, ConfigurationSource.Convention)); diff --git a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs index fce8058cbc6..9b3ca4ef12e 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs @@ -20,7 +20,7 @@ public abstract class ManyToManyTestBase : ModelBuilderTestBase public virtual void Finds_existing_navigations_and_uses_associated_FK() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; + var model = (IModel)modelBuilder.Model; modelBuilder.Entity().Ignore(c => c.Products); modelBuilder.Entity().Ignore(p => p.Categories); @@ -52,7 +52,7 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() pcb => pcb.HasOne(pc => pc.Product).WithMany(), pcb => pcb.HasOne(pc => pc.Category).WithMany(c => c.ProductCategories)); - modelBuilder.FinalizeModel(); + model = modelBuilder.FinalizeModel(); Assert.Same(categoriesNavigation, productType.GetSkipNavigations().Single()); Assert.Same(productsNavigation, categoryType.GetSkipNavigations().Single()); @@ -65,7 +65,7 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK() public virtual void Finds_existing_navigations_and_uses_associated_FK_with_fields() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; + var model = (IModel)modelBuilder.Model; modelBuilder.Entity(e => { @@ -115,7 +115,7 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK_with_field jwf => jwf.HasOne(j => j.ManyToManyPrincipalWithField) .WithMany()); - modelBuilder.FinalizeModel(); + model = modelBuilder.FinalizeModel(); Assert.Same(principalToJoinNav, principalEntityType.GetSkipNavigations().Single()); Assert.Same(dependentToJoinNav, dependentEntityType.GetSkipNavigations().Single()); @@ -128,10 +128,11 @@ public virtual void Finds_existing_navigations_and_uses_associated_FK_with_field public virtual void Join_type_is_automatically_configured_by_convention() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity(); + var model = modelBuilder.FinalizeModel(); + var manyToManyA = model.FindEntityType(typeof(ImplicitManyToManyA)); var manyToManyB = model.FindEntityType(typeof(ImplicitManyToManyB)); var joinEntityType = model.GetEntityTypes() @@ -157,11 +158,9 @@ public virtual void Join_type_is_automatically_configured_by_convention() var key = joinEntityType.FindPrimaryKey(); Assert.Equal( new[] { - nameof(ImplicitManyToManyA) + "_" + nameof(ImplicitManyToManyA.Id), - nameof(ImplicitManyToManyB) + "_" + nameof(ImplicitManyToManyB.Id) }, + nameof(ImplicitManyToManyA) + nameof(ImplicitManyToManyA.Id), + nameof(ImplicitManyToManyB) + nameof(ImplicitManyToManyB.Id) }, key.Properties.Select(p => p.Name)); - - modelBuilder.FinalizeModel(); } [ConditionalFact] @@ -174,8 +173,6 @@ public virtual void Join_type_is_not_automatically_configured_when_navigations_a var hob = model.FindEntityType(typeof(Hob)); var nob = model.FindEntityType(typeof(Nob)); - Assert.NotNull(hob); - Assert.NotNull(nob); Assert.Empty(model.GetEntityTypes() .Where(et => ((EntityType)et).IsImplicitlyCreatedJoinEntityType)); @@ -187,7 +184,6 @@ public virtual void Join_type_is_not_automatically_configured_when_navigations_a public virtual void Can_configure_join_type_using_fluent_api() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity().Ignore(c => c.Products); modelBuilder.Entity().Ignore(p => p.Categories); @@ -199,7 +195,7 @@ public virtual void Can_configure_join_type_using_fluent_api() pcb => pcb.HasOne(pc => pc.Category).WithMany(c => c.ProductCategories), pcb => pcb.HasKey(pc => new { pc.ProductId, pc.CategoryId })); - modelBuilder.FinalizeModel(); + var model = modelBuilder.FinalizeModel(); Assert.Equal(typeof(Category), manyToMany.Metadata.ClrType); @@ -227,7 +223,6 @@ public virtual void Can_configure_join_type_using_fluent_api() public virtual void Can_ignore_existing_navigations() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity() .HasMany(p => p.Products).WithMany(c => c.Categories); @@ -237,20 +232,19 @@ public virtual void Can_ignore_existing_navigations() // Issue #19550 modelBuilder.Ignore(); + var model = modelBuilder.FinalizeModel(); + var productType = model.FindEntityType(typeof(Product)); var categoryType = model.FindEntityType(typeof(Category)); Assert.Empty(productType.GetSkipNavigations()); Assert.Empty(categoryType.GetSkipNavigations()); - - modelBuilder.FinalizeModel(); } [ConditionalFact] public virtual void Throws_for_conflicting_many_to_one_on_left() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; // make sure we do not set up the automatic many-to-many relationship modelBuilder.Entity().Ignore(e => e.Products); @@ -273,7 +267,6 @@ public virtual void Throws_for_conflicting_many_to_one_on_left() public virtual void Throws_for_conflicting_many_to_one_on_right() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; // make sure we do not set up the automatic many-to-many relationship modelBuilder.Entity().Ignore(e => e.Products); @@ -296,7 +289,6 @@ public virtual void Throws_for_conflicting_many_to_one_on_right() public virtual void Throws_for_many_to_many_with_only_one_navigation_configured() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; Assert.Equal( CoreStrings.MissingInverseManyToManyNavigation( @@ -312,7 +304,6 @@ public virtual void Throws_for_many_to_many_with_only_one_navigation_configured( public virtual void Navigation_properties_can_set_access_mode_using_expressions() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity() .HasMany(e => e.Dependents) @@ -326,8 +317,13 @@ public virtual void Navigation_properties_can_set_access_mode_using_expressions( .Navigation(e => e.ManyToManyPrincipals) .UsePropertyAccessMode(PropertyAccessMode.Property); - var principal = (IEntityType)model.FindEntityType(typeof(ManyToManyNavPrincipal)); - var dependent = (IEntityType)model.FindEntityType(typeof(NavDependent)); + modelBuilder.Entity() + .Ignore(n => n.OneToOnePrincipal); + + var model = modelBuilder.FinalizeModel(); + + var principal = model.FindEntityType(typeof(ManyToManyNavPrincipal)); + var dependent = model.FindEntityType(typeof(NavDependent)); Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode()); Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode()); @@ -337,7 +333,6 @@ public virtual void Navigation_properties_can_set_access_mode_using_expressions( public virtual void Navigation_properties_can_set_access_mode_using_navigation_names() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity() .HasMany("Dependents") @@ -351,6 +346,11 @@ public virtual void Navigation_properties_can_set_access_mode_using_navigation_n .Navigation("ManyToManyPrincipals") .UsePropertyAccessMode(PropertyAccessMode.Property); + modelBuilder.Entity() + .Ignore(n => n.OneToOnePrincipal); + + var model = modelBuilder.FinalizeModel(); + var principal = (IEntityType)model.FindEntityType(typeof(ManyToManyNavPrincipal)); var dependent = (IEntityType)model.FindEntityType(typeof(NavDependent)); @@ -362,7 +362,6 @@ public virtual void Navigation_properties_can_set_access_mode_using_navigation_n public virtual void Can_use_shared_Type_as_join_entity() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Ignore(); modelBuilder.Ignore(); @@ -384,13 +383,20 @@ public virtual void Can_use_shared_Type_as_join_entity() e => e.HasOne().WithMany(), e => e.IndexerProperty("Payload")); - var shared1 = modelBuilder.Model.FindEntityType("Shared1"); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.DependentWithFieldId); + + var model = modelBuilder.FinalizeModel(); + + var shared1 = model.FindEntityType("Shared1"); Assert.NotNull(shared1); Assert.Equal(2, shared1.GetForeignKeys().Count()); Assert.True(shared1.HasSharedClrType); Assert.Equal(typeof(Dictionary), shared1.ClrType); - var shared2 = modelBuilder.Model.FindEntityType("Shared2"); + var shared2 = model.FindEntityType("Shared2"); Assert.NotNull(shared2); Assert.Equal(2, shared2.GetForeignKeys().Count()); Assert.True(shared2.HasSharedClrType); @@ -434,6 +440,13 @@ public virtual void UsingEntity_with_shared_type_passed_when_marked_as_shared_ty r => r.HasOne().WithMany(), l => l.HasOne().WithMany()).Metadata; + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.DependentWithFieldId); + + var model = modelBuilder.FinalizeModel(); + Assert.True(joinEntityType.HasSharedClrType); Assert.Equal("Shared", joinEntityType.Name); Assert.Equal(typeof(ManyToManyJoinWithFields), joinEntityType.ClrType); @@ -454,6 +467,13 @@ public virtual void UsingEntity_with_shared_type_passes_when_configured_as_share r => r.HasOne().WithMany(), l => l.HasOne().WithMany()).Metadata; + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.Id); + modelBuilder.Entity().HasKey(d => d.DependentWithFieldId); + + var model = modelBuilder.FinalizeModel(); + Assert.True(joinEntityType.HasSharedClrType); Assert.Equal("Shared", joinEntityType.Name); Assert.Equal(typeof(ManyToManyJoinWithFields), joinEntityType.ClrType);