From af22c0e132919623a11d35decb1c82fc6443d315 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 7 Dec 2020 21:49:34 -0800 Subject: [PATCH] Use shared-type entity types instead of entity types with defining navigation. They should behave mostly the same, except that DefiningEntityType and DefiningNavigationName will return true and Name will return what FullName returned previously Fixes #22378 --- .../Design/CSharpSnapshotGenerator.cs | 19 +- ...yableMethodTranslatingExpressionVisitor.cs | 3 +- .../Properties/ProxiesStrings.Designer.cs | 1 + .../Properties/ProxiesStrings.resx | 1 + .../Proxies/Internal/ProxyFactory.cs | 5 - .../RelationalEntityTypeExtensions.cs | 42 +- .../Metadata/Internal/RelationalModel.cs | 3 +- .../Internal/MigrationsModelDiffer.cs | 31 +- ...yableMethodTranslatingExpressionVisitor.cs | 3 +- .../Internal/ModificationCommandComparer.cs | 16 +- .../Internal/EntityReferenceMap.cs | 58 +-- .../Internal/NavigationFixer.cs | 3 +- .../ChangeTracking/Internal/StateManager.cs | 29 +- src/EFCore/DbContext.cs | 5 - .../Diagnostics/CoreLoggerExtensions.cs | 1 + .../ConventionEntityTypeExtensions.cs | 1 + .../Extensions/ConventionModelExtensions.cs | 11 +- src/EFCore/Extensions/EntityTypeExtensions.cs | 102 +++-- src/EFCore/Extensions/ModelExtensions.cs | 13 +- .../Extensions/MutableEntityTypeExtensions.cs | 1 + .../Extensions/MutableModelExtensions.cs | 11 +- src/EFCore/Infrastructure/ModelValidator.cs | 94 ++-- src/EFCore/Internal/EntityFinder.cs | 14 +- src/EFCore/Internal/InternalDbSet.cs | 5 - .../Builders/CollectionCollectionBuilder`.cs | 2 +- .../Metadata/Builders/EntityTypeBuilder.cs | 20 +- .../Builders/OwnedNavigationBuilder.cs | 48 +- .../Builders/ReferenceReferenceBuilder.cs | 15 +- .../BaseTypeDiscoveryConvention.cs | 4 +- .../DerivedTypeDiscoveryConvention.cs | 4 +- .../ForeignKeyPropertyDiscoveryConvention.cs | 26 +- .../ProviderConventionSetBuilder.cs | 1 - .../InversePropertyAttributeConvention.cs | 26 +- .../Conventions/KeyDiscoveryConvention.cs | 21 +- .../Conventions/ModelCleanupConvention.cs | 3 +- .../Conventions/OwnedTypesConvention.cs | 20 +- .../RelationshipDiscoveryConvention.cs | 37 +- .../Metadata/EntityTypeFullNameComparer.cs | 52 +-- src/EFCore/Metadata/IConventionEntityType.cs | 3 +- src/EFCore/Metadata/IEntityType.cs | 21 +- src/EFCore/Metadata/IModel.cs | 10 +- src/EFCore/Metadata/IMutableEntityType.cs | 3 +- src/EFCore/Metadata/Internal/EntityType.cs | 118 +---- .../Metadata/Internal/EntityTypeExtensions.cs | 92 +--- src/EFCore/Metadata/Internal/ForeignKey.cs | 21 +- .../Internal/InternalEntityTypeBuilder.cs | 415 ++++++++---------- .../Internal/InternalForeignKeyBuilder.cs | 268 +++++------ .../Metadata/Internal/InternalModelBuilder.cs | 211 ++++----- src/EFCore/Metadata/Internal/Model.cs | 270 +++--------- .../Metadata/Internal/RelationshipSnapshot.cs | 8 +- src/EFCore/Properties/CoreStrings.Designer.cs | 22 +- src/EFCore/Properties/CoreStrings.resx | 18 +- ...ingExpressionVisitor.ExpressionVisitors.cs | 5 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 6 +- ...igationsSharedTypeQueryInMemoryFixture.cs} | 2 +- ...NavigationsSharedTypeQueryInMemoryTest.cs} | 8 +- test/EFCore.Proxies.Tests/ProxyTests.cs | 18 - ...tionsSharedQueryTypeRelationalTestBase.cs} | 6 +- ...nsSharedTypeQueryRelationalFixtureBase.cs} | 2 +- .../RelationalMetadataExtensionsTest.cs | 22 - .../Metadata/RelationalModelTest.cs | 4 +- ...xNavigationsSharedTypeQueryFixtureBase.cs} | 3 +- ...plexNavigationsSharedTypeQueryTestBase.cs} | 34 +- .../Query/OwnedQueryTestBase.cs | 11 - ...gationsSharedTypeQuerySqlServerFixture.cs} | 2 +- ...NavigationsSharedTypeQuerySqlServerTest.cs | 293 +++++++++++++ ...omplexNavigationsWeakQuerySqlServerTest.cs | 180 -------- ...avigationsSharedTypeQuerySqliteFixture.cs} | 2 +- ...exNavigationsSharedTypeQuerySqliteTest.cs} | 24 +- .../ChangeTracking/Internal/OwnedFixupTest.cs | 279 ++++++------ .../ChangeTracking/Internal/QueryFixupTest.cs | 13 +- test/EFCore.Tests/DbContextTest.cs | 17 - .../Infrastructure/ModelValidatorTest.cs | 108 ----- .../Conventions/ConventionDispatcherTest.cs | 86 +--- ...reignKeyPropertyDiscoveryConventionTest.cs | 28 -- .../NavigationAttributeConventionTest.cs | 39 -- .../Metadata/Internal/EntityTypeTest.cs | 115 ++--- .../Internal/InternalModelBuilderTest.cs | 19 +- .../Metadata/Internal/ModelTest.cs | 56 +-- .../ModelBuilding/InheritanceTestBase.cs | 2 +- .../ModelBuilding/ManyToManyTestBase.cs | 3 +- .../ModelBuilderNonGenericStringTest.cs | 7 +- .../ModelBuilding/NonRelationshipTestBase.cs | 4 +- .../ModelBuilding/OneToManyTestBase.cs | 2 +- .../ModelBuilding/OneToOneTestBase.cs | 2 +- .../ModelBuilding/OwnedTypesTestBase.cs | 46 +- .../ModelBuilding/ShadowEntityTypeTest.cs | 8 +- 87 files changed, 1450 insertions(+), 2237 deletions(-) rename test/EFCore.InMemory.FunctionalTests/Query/{ComplexNavigationsWeakQueryInMemoryFixture.cs => ComplexNavigationsSharedTypeQueryInMemoryFixture.cs} (77%) rename test/EFCore.InMemory.FunctionalTests/Query/{ComplexNavigationsWeakQueryInMemoryTest.cs => ComplexNavigationsSharedTypeQueryInMemoryTest.cs} (92%) rename test/EFCore.Relational.Specification.Tests/Query/{ComplexNavigationsWeakQueryRelationalTestBase.cs => ComplexNavigationsSharedQueryTypeRelationalTestBase.cs} (98%) rename test/EFCore.Relational.Specification.Tests/Query/{ComplexNavigationsWeakQueryRelationalFixtureBase.cs => ComplexNavigationsSharedTypeQueryRelationalFixtureBase.cs} (91%) rename test/EFCore.Specification.Tests/Query/{ComplexNavigationsWeakQueryFixtureBase.cs => ComplexNavigationsSharedTypeQueryFixtureBase.cs} (99%) rename test/EFCore.Specification.Tests/Query/{ComplexNavigationsWeakQueryTestBase.cs => ComplexNavigationsSharedTypeQueryTestBase.cs} (84%) rename test/EFCore.SqlServer.FunctionalTests/Query/{ComplexNavigationsWeakQuerySqlServerFixture.cs => ComplexNavigationsSharedTypeQuerySqlServerFixture.cs} (76%) create mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs rename test/EFCore.Sqlite.FunctionalTests/Query/{ComplexNavigationsWeakQuerySqliteFixture.cs => ComplexNavigationsSharedTypeQuerySqliteFixture.cs} (76%) rename test/EFCore.Sqlite.FunctionalTests/Query/{ComplexNavigationsWeakQuerySqliteTest.cs => ComplexNavigationsSharedTypeQuerySqliteTest.cs} (70%) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index af0a260930d..0c2e11616ee 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -121,8 +121,7 @@ protected virtual void GenerateEntityTypes( Check.NotNull(stringBuilder, nameof(stringBuilder)); foreach (var entityType in entityTypes.Where( - e => !e.HasDefiningNavigation() - && e.FindOwnership() == null)) + e => e.FindOwnership() == null)) { stringBuilder.AppendLine(); @@ -130,8 +129,7 @@ protected virtual void GenerateEntityTypes( } foreach (var entityType in entityTypes.Where( - e => !e.HasDefiningNavigation() - && e.FindOwnership() == null + e => e.FindOwnership() == null && (e.GetDeclaredForeignKeys().Any() || e.GetDeclaredReferencingForeignKeys().Any(fk => fk.IsOwnership)))) { @@ -141,8 +139,7 @@ protected virtual void GenerateEntityTypes( } foreach (var entityType in entityTypes.Where( - e => !e.HasDefiningNavigation() - && e.FindOwnership() == null + e => e.FindOwnership() == null && e.GetDeclaredNavigations().Any(n => !n.IsOnDependent && !n.ForeignKey.IsOwnership))) { stringBuilder.AppendLine(); @@ -169,13 +166,21 @@ protected virtual void GenerateEntityType( var ownership = entityType.FindOwnership(); var ownerNavigation = ownership?.PrincipalToDependent.Name; + var entityTypeName = entityType.Name; + if (ownerNavigation != null + && entityType.HasSharedClrType + && entityTypeName == ownership.PrincipalEntityType.GetOwnedName(entityType.ClrType.ShortDisplayName(), ownerNavigation)) + { + entityTypeName = entityType.ClrType.DisplayName(); + } + stringBuilder .Append(builderName) .Append( ownerNavigation != null ? ownership.IsUnique ? ".OwnsOne(" : ".OwnsMany(" : ".Entity(") - .Append(Code.Literal(entityType.Name)); + .Append(Code.Literal(entityTypeName)); if (ownerNavigation != null) { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 3a47094ab04..6b6cadbc927 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -1399,8 +1399,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var targetEntityType = navigation.TargetEntityType; if (targetEntityType == null - || (!targetEntityType.HasDefiningNavigation() - && !targetEntityType.IsOwned())) + || !targetEntityType.IsOwned()) { return null; } diff --git a/src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs b/src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs index a365c773f4b..eab3a82587d 100644 --- a/src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs +++ b/src/EFCore.Proxies/Properties/ProxiesStrings.Designer.cs @@ -39,6 +39,7 @@ public static string EntityTypeNotFoundShared([CanBeNull] object? clrType) /// /// Cannot create a proxy for '{typeName}' because it is mapped to multiple owned entity types. Proxy creation is not supported for owned types used more than once in the model. /// + [Obsolete] public static string EntityTypeNotFoundWeak([CanBeNull] object? typeName) => string.Format( GetString("EntityTypeNotFoundWeak", nameof(typeName)), diff --git a/src/EFCore.Proxies/Properties/ProxiesStrings.resx b/src/EFCore.Proxies/Properties/ProxiesStrings.resx index f6c2654247f..5b15a15a34d 100644 --- a/src/EFCore.Proxies/Properties/ProxiesStrings.resx +++ b/src/EFCore.Proxies/Properties/ProxiesStrings.resx @@ -125,6 +125,7 @@ Cannot create a proxy for '{typeName}' because it is mapped to multiple owned entity types. Proxy creation is not supported for owned types used more than once in the model. + Obsolete Property '{property}' on entity type '{entityType}' is mapped without a CLR property. 'UseChangeTrackingProxies' requires all entity types to be public, unsealed, have virtual properties, and have a public or protected constructor. 'UseLazyLoadingProxies' requires only the navigation properties be virtual. diff --git a/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs b/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs index b4198381f78..aae68a69ba8 100644 --- a/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs +++ b/src/EFCore.Proxies/Proxies/Internal/ProxyFactory.cs @@ -46,11 +46,6 @@ public virtual object Create( throw new InvalidOperationException(ProxiesStrings.EntityTypeNotFoundShared(type.ShortDisplayName())); } - if (context.Model.HasEntityTypeWithDefiningNavigation(type)) - { - throw new InvalidOperationException(ProxiesStrings.EntityTypeNotFoundWeak(type.ShortDisplayName())); - } - throw new InvalidOperationException(CoreStrings.EntityTypeNotFound(type.ShortDisplayName())); } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 211ad8c6650..5cfdaab23dc 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -63,12 +63,16 @@ public static string GetDefaultTableName([NotNull] this IEntityType entityType, } var name = entityType.ShortName(); - if (entityType.HasDefiningNavigation()) + if (entityType.HasSharedClrType + && ownership != null +#pragma warning disable EF1001 // Internal EF Core API usage. + && entityType.Name == ownership.PrincipalEntityType.GetOwnedName(name, ownership.PrincipalToDependent.Name)) +#pragma warning restore EF1001 // Internal EF Core API usage. { - var definingTypeName = entityType.DefiningEntityType.GetTableName(); - name = definingTypeName != null - ? $"{definingTypeName}_{entityType.DefiningNavigationName}" - : $"{entityType.DefiningNavigationName}_{name}"; + var ownerTypeTable = ownership.PrincipalEntityType.GetTableName(); + name = ownerTypeTable != null + ? $"{ownerTypeTable}_{ownership.PrincipalToDependent.Name}" + : $"{ownership.PrincipalToDependent.Name}_{name}"; } return truncate @@ -141,31 +145,23 @@ public static string GetSchema([NotNull] this IEntityType entityType) public static string GetDefaultSchema([NotNull] this IEntityType entityType) { var ownership = entityType.FindOwnership(); - if (ownership != null - && ownership.IsUnique) + if (ownership != null) { return ownership.PrincipalEntityType.GetSchema(); } - if (entityType.HasDefiningNavigation()) + var skipNavigationSchema = entityType.GetForeignKeys().SelectMany(fk => fk.GetReferencingSkipNavigations()) + .FirstOrDefault(n => !n.IsOnDependent) + ?.DeclaringEntityType.GetSchema(); + if (skipNavigationSchema != null + && entityType.GetForeignKeys().SelectMany(fk => fk.GetReferencingSkipNavigations()) + .Where(n => !n.IsOnDependent) + .All(n => n.DeclaringEntityType.GetSchema() == skipNavigationSchema)) { - return entityType.DefiningEntityType.GetSchema() ?? entityType.Model.GetDefaultSchema(); + return skipNavigationSchema; } - else - { - var skipReferencingTypes = entityType.GetForeignKeys().SelectMany(fk => fk.GetReferencingSkipNavigations()) - .Where(n => !n.IsOnDependent && n.DeclaringEntityType != entityType) - .ToList(); - var skipNavigationSchema = skipReferencingTypes.FirstOrDefault()?.DeclaringEntityType.GetSchema(); - if (skipNavigationSchema != null - && skipReferencingTypes.Skip(1) - .All(n => n.DeclaringEntityType.GetSchema() == skipNavigationSchema)) - { - return skipNavigationSchema; - } - return entityType.Model.GetDefaultSchema(); - } + return entityType.Model.GetDefaultSchema(); } /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 766a03f1929..49c4cf841d3 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -230,7 +230,8 @@ public static IModel Add( private static void AddDefaultMappings(RelationalModel databaseModel, IConventionEntityType entityType) { - var name = entityType.GetRootType().FullName(); + var rootType = entityType.GetRootType(); + var name = rootType.HasSharedClrType ? rootType.FullName() : rootType.ShortName(); if (!databaseModel.DefaultTables.TryGetValue(name, out var defaultTable)) { defaultTable = new TableBase(name, null, databaseModel); diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 606934bcafc..ed8e9dd6a4d 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -947,39 +947,24 @@ private static bool EntityTypePathEquals(IEntityType source, IEntityType target, return false; } - if (GetDefiningNavigationName(source) != GetDefiningNavigationName(target)) - { - return false; - } - - var nextSource = source.DefiningEntityType; - var nextTarget = target.DefiningEntityType; + var nextSource = GetLinkedPrincipal(source); + var nextTarget = GetLinkedPrincipal(target); return (nextSource == null && nextTarget == null) || (nextSource != null && nextTarget != null && EntityTypePathEquals(nextSource, nextTarget, diffContext)); } - private static string GetDefiningNavigationName(IEntityType entityType) + private static IEntityType GetLinkedPrincipal(IEntityType entityType) { - if (entityType.DefiningNavigationName != null) + var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); + if (table == null) { - return entityType.DefiningNavigationName; - } - - var primaryKey = entityType.BaseType == null ? entityType.FindPrimaryKey() : null; - if (primaryKey != null) - { - var definingForeignKey = entityType - .FindForeignKeys(primaryKey.Properties) - .FirstOrDefault(fk => fk.PrincipalEntityType.GetTableName() == entityType.GetTableName()); - if (definingForeignKey?.DependentToPrincipal != null) - { - return definingForeignKey.DependentToPrincipal.Name; - } + return null; } - return entityType.Name; + var linkingForeignKey = entityType.FindRowInternalForeignKeys(table.Value).FirstOrDefault(); + return linkingForeignKey?.PrincipalEntityType; } /// diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 6733c34d8fc..b7617837b82 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -1349,8 +1349,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var targetEntityType = navigation.TargetEntityType; if (targetEntityType == null - || (!targetEntityType.HasDefiningNavigation() - && !targetEntityType.IsOwned())) + || !targetEntityType.IsOwned()) { return null; } diff --git a/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs b/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs index 430086c6877..14517311d54 100644 --- a/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs +++ b/src/EFCore.Relational/Update/Internal/ModificationCommandComparer.cs @@ -47,20 +47,20 @@ public virtual int Compare(ModificationCommand x, ModificationCommand y) } result = StringComparer.Ordinal.Compare(x.Schema, y.Schema); - if (0 != result) + if (result != 0) { return result; } result = StringComparer.Ordinal.Compare(x.TableName, y.TableName); - if (0 != result) + if (result != 0) { return result; } var xState = x.EntityState; result = (int)xState - (int)y.EntityState; - if (0 != result) + if (result != 0) { return result; } @@ -77,13 +77,7 @@ public virtual int Compare(ModificationCommand x, ModificationCommand y) if (xEntityType != yEntityType) { result = StringComparer.Ordinal.Compare(xEntityType.Name, yEntityType.Name); - if (0 != result) - { - return result; - } - - result = StringComparer.Ordinal.Compare(xEntityType.DefiningNavigationName, yEntityType.DefiningNavigationName); - if (0 != result) + if (result != 0) { return result; } @@ -95,7 +89,7 @@ public virtual int Compare(ModificationCommand x, ModificationCommand y) var xKeyProperty = xKey.Properties[i]; result = xKeyProperty.GetCurrentValueComparer().Compare(xEntry, yEntry); - if (0 != result) + if (result != 0) { return result; } diff --git a/src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs b/src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs index 7d8e53c4087..ef743d3716e 100644 --- a/src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs +++ b/src/EFCore/ChangeTracking/Internal/EntityReferenceMap.cs @@ -26,7 +26,7 @@ public class EntityReferenceMap private Dictionary _addedReferenceMap; private Dictionary _modifiedReferenceMap; private Dictionary _deletedReferenceMap; - private Dictionary _dependentTypeReferenceMap; + private Dictionary _sharedTypeReferenceMap; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -52,20 +52,20 @@ public virtual void Update( { var entityType = entry.EntityType; if (_hasSubMap - && entityType.HasDefiningNavigation()) + && entityType.HasSharedClrType) { - if (_dependentTypeReferenceMap == null) + if (_sharedTypeReferenceMap == null) { - _dependentTypeReferenceMap = new Dictionary(); + _sharedTypeReferenceMap = new Dictionary(); } - if (!_dependentTypeReferenceMap.TryGetValue(entityType, out var dependentMap)) + if (!_sharedTypeReferenceMap.TryGetValue(entityType, out var sharedMap)) { - dependentMap = new EntityReferenceMap(hasSubMap: false); - _dependentTypeReferenceMap[entityType] = dependentMap; + sharedMap = new EntityReferenceMap(hasSubMap: false); + _sharedTypeReferenceMap[entityType] = sharedMap; } - dependentMap.Update(entry, state, oldState); + sharedMap.Update(entry, state, oldState); } else { @@ -128,11 +128,11 @@ public virtual bool TryGet( if (!found && _hasSubMap - && _dependentTypeReferenceMap != null) + && _sharedTypeReferenceMap != null) { if (entityType != null) { - if (_dependentTypeReferenceMap.TryGetValue(entityType, out var subMap)) + if (_sharedTypeReferenceMap.TryGetValue(entityType, out var subMap)) { return subMap.TryGet(entity, entityType, out entry, throwOnNonUniqueness); } @@ -140,7 +140,7 @@ public virtual bool TryGet( else { var type = entity.GetType(); - foreach (var keyValue in _dependentTypeReferenceMap) + foreach (var keyValue in _sharedTypeReferenceMap) { // ReSharper disable once CheckForReferenceEqualityInstead.2 if (keyValue.Key.ClrType.IsAssignableFrom(type) @@ -208,9 +208,9 @@ public virtual int GetCountForState( count += _unchangedReferenceMap.Count; } - if (_dependentTypeReferenceMap != null) + if (_sharedTypeReferenceMap != null) { - foreach (var map in _dependentTypeReferenceMap) + foreach (var map in _sharedTypeReferenceMap) { count += map.Value.GetCountForState(added, modified, deleted, unchanged); } @@ -253,11 +253,11 @@ var returnUnchanged && _unchangedReferenceMap != null && _unchangedReferenceMap.Count > 0; - var hasDependentTypes - = _dependentTypeReferenceMap != null - && _dependentTypeReferenceMap.Count > 0; + var hasSharedTypes + = _sharedTypeReferenceMap != null + && _sharedTypeReferenceMap.Count > 0; - if (!hasDependentTypes) + if (!hasSharedTypes) { var numberOfStates = (returnAdded ? 1 : 0) @@ -296,7 +296,7 @@ var numberOfStates return GetEntriesForState( added, modified, deleted, unchanged, - hasDependentTypes, + hasSharedTypes, returnAdded, returnModified, returnDeleted, returnUnchanged); } @@ -305,7 +305,7 @@ private IEnumerable GetEntriesForState( bool modified, bool deleted, bool unchanged, - bool hasDependentTypes, + bool hasSharedTypes, bool returnAdded, bool returnModified, bool returnDeleted, @@ -343,9 +343,9 @@ private IEnumerable GetEntriesForState( } } - if (hasDependentTypes) + if (hasSharedTypes) { - foreach (var subMap in _dependentTypeReferenceMap.Values) + foreach (var subMap in _sharedTypeReferenceMap.Values) { foreach (var entry in subMap.GetEntriesForState(added, modified, deleted, unchanged)) { @@ -364,10 +364,10 @@ private void Remove( IEntityType entityType, EntityState oldState) { - if (_dependentTypeReferenceMap != null - && entityType.HasDefiningNavigation()) + if (_sharedTypeReferenceMap != null + && entityType.HasSharedClrType) { - _dependentTypeReferenceMap[entityType].Remove(entity, entityType, oldState); + _sharedTypeReferenceMap[entityType].Remove(entity, entityType, oldState); } else { @@ -405,8 +405,8 @@ public virtual void Clear() _deletedReferenceMap = null; _addedReferenceMap = null; _modifiedReferenceMap = null; - _dependentTypeReferenceMap?.Clear(); - _dependentTypeReferenceMap = null; + _sharedTypeReferenceMap?.Clear(); + _sharedTypeReferenceMap = null; } /// @@ -456,10 +456,10 @@ public virtual IEnumerable GetNonDeletedEntities() } } - if (_dependentTypeReferenceMap != null - && _dependentTypeReferenceMap.Count > 0) + if (_sharedTypeReferenceMap != null + && _sharedTypeReferenceMap.Count > 0) { - foreach (var subMap in _dependentTypeReferenceMap.Values) + foreach (var subMap in _sharedTypeReferenceMap.Values) { foreach (var entity in subMap.GetNonDeletedEntities()) { diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index a2372cff602..ad7d7bd90ab 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -158,8 +158,7 @@ public virtual void NavigationReferenceChanged( // Clear the inverse reference, unless it has already been changed if (inverse != null && ReferenceEquals(oldTargetEntry[inverse], entry.Entity) - && (!oldTargetEntry.EntityType.HasDefiningNavigation() - || entry.EntityType.GetNavigations().All( + && (entry.EntityType.GetNavigations().All( n => n == navigation || !ReferenceEquals(oldTargetEntry.Entity, entry[n])))) { diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 088d8921712..5c3f4a14885 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -37,8 +37,7 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal /// public class StateManager : IStateManager { - private readonly EntityReferenceMap _entityReferenceMap - = new EntityReferenceMap(hasSubMap: true); + private readonly EntityReferenceMap _entityReferenceMap = new(hasSubMap: true); private IDictionary>> _referencedUntrackedEntities; private IIdentityMap _identityMap0; @@ -211,7 +210,7 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity) var entityType = _model.FindRuntimeEntityType(entity.GetType()); if (entityType == null) { - if (_model.HasEntityTypeWithDefiningNavigation(entity.GetType())) + if (_model.IsShared(entity.GetType())) { throw new InvalidOperationException( CoreStrings.UntrackedDependentEntity( @@ -252,20 +251,17 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity, IEntityType e var entry = TryGetEntry(entity, entityType); if (entry == null) { - if (!entityType.HasSharedClrType) + var runtimeEntityType = _model.FindRuntimeEntityType(entity.GetType()); + if (runtimeEntityType != null) { - var runtimeEntityType = _model.FindRuntimeEntityType(entity.GetType()); - if (runtimeEntityType != null) + if (!entityType.IsAssignableFrom(runtimeEntityType)) { - if (!entityType.IsAssignableFrom(runtimeEntityType)) - { - throw new InvalidOperationException( - CoreStrings.TrackingTypeMismatch( - runtimeEntityType.DisplayName(), entityType.DisplayName())); - } - - entityType = runtimeEntityType; + throw new InvalidOperationException( + CoreStrings.TrackingTypeMismatch( + runtimeEntityType.DisplayName(), entityType.DisplayName())); } + + entityType = runtimeEntityType; } if (entityType.FindPrimaryKey() == null) @@ -325,10 +321,10 @@ private void UpdateReferenceMaps( EntityState? oldState) { var entityType = entry.EntityType; - if (entityType.HasDefiningNavigation()) + if (entityType.HasSharedClrType) { var mapKey = entry.Entity ?? entry; - foreach (var otherType in _model.GetEntityTypes(entityType.Name) + foreach (var otherType in _model.GetEntityTypes(entityType.ClrType) .Where(et => et != entityType && TryGetEntry(mapKey, et) != null)) { UpdateLogger.DuplicateDependentEntityTypeInstanceWarning(entityType, otherType); @@ -358,7 +354,6 @@ public virtual InternalEntityEntry StartTrackingFromQuery( var clrType = entity.GetType(); var entityType = baseEntityType.HasSharedClrType || baseEntityType.ClrType == clrType - || baseEntityType.HasDefiningNavigation() ? baseEntityType : _model.FindRuntimeEntityType(clrType); diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index e33c115bf69..aba4e94c7d7 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -306,11 +306,6 @@ private IEntityFinder Finder(Type type) var entityType = Model.FindEntityType(type); if (entityType == null) { - if (Model.HasEntityTypeWithDefiningNavigation(type)) - { - throw new InvalidOperationException(CoreStrings.InvalidSetTypeWeak(type.ShortDisplayName())); - } - if (Model.IsShared(type)) { throw new InvalidOperationException(CoreStrings.InvalidSetSharedType(type.ShortDisplayName())); diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index 904ac5295d0..13e3a87bacf 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -1756,6 +1756,7 @@ private static string MultipleInversePropertiesSameTargetWarning(EventDefinition /// The target type. /// The inverse navigation property. /// The defining navigation property. + [Obsolete] public static void NonDefiningInverseNavigationWarning( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] IEntityType declaringType, diff --git a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs index 3ac62ffd88f..34f5113267c 100644 --- a/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/ConventionEntityTypeExtensions.cs @@ -417,6 +417,7 @@ public static IEnumerable GetDeclaredReferencingForeignKe /// /// The entity type. /// The defining navigation if one exists or otherwise. + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] public static IConventionNavigation? FindDefiningNavigation([NotNull] this IConventionEntityType entityType) => (IConventionNavigation?)((IEntityType)entityType).FindDefiningNavigation(); diff --git a/src/EFCore/Extensions/ConventionModelExtensions.cs b/src/EFCore/Extensions/ConventionModelExtensions.cs index 68545e8123b..b8a2d59a79a 100644 --- a/src/EFCore/Extensions/ConventionModelExtensions.cs +++ b/src/EFCore/Extensions/ConventionModelExtensions.cs @@ -49,7 +49,7 @@ public static IConventionEntityType FindEntityType( /// The type of the entity type to find. /// The entity types found. [DebuggerStepThrough] - public static IReadOnlyCollection GetEntityTypes([NotNull] this IConventionModel model, [NotNull] Type type) + public static IEnumerable GetEntityTypes([NotNull] this IConventionModel model, [NotNull] Type type) => ((Model)model).GetEntityTypes(type); /// @@ -59,6 +59,7 @@ public static IReadOnlyCollection GetEntityTypes([NotNull /// The name of the entity type to find. /// The entity types found. [DebuggerStepThrough] + [Obsolete("Use GetEntityTypes(Type) or FindEntityType(string)")] public static IReadOnlyCollection GetEntityTypes( [NotNull] this IConventionModel model, [NotNull] string name) @@ -81,7 +82,8 @@ public static IConventionEntityType RemoveEntityType( } /// - /// Removes an entity type with a defining navigation from the model. + /// Removes an entity type with the given type, defining navigation name + /// and the defining entity type. /// /// The model to remove the entity type from. /// The name of the entity type to be removed. @@ -99,7 +101,7 @@ public static IConventionEntityType RemoveEntityType( Check.NotEmpty(definingNavigationName, nameof(definingNavigationName)); Check.NotNull(definingEntityType, nameof(definingEntityType)); - return ((Model)model).RemoveEntityType(name); + return ((Model)model).RemoveEntityType(name, definingNavigationName, definingEntityType); } /// @@ -117,7 +119,8 @@ public static IConventionEntityType RemoveEntityType([NotNull] this IConventionM } /// - /// Removes an entity type with a defining navigation from the model. + /// Removes an entity type with the given type, defining navigation name + /// and the defining entity type. /// /// The model to remove the entity type from. /// The CLR class that is used to represent instances of this entity type. diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index e232ed1e846..a7ee05696b3 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -322,11 +322,6 @@ public static IEnumerable GetDeclaredServiceProperties([NotNul public static IEnumerable GetDeclaredIndexes([NotNull] this IEntityType entityType) => entityType.AsEntityType().GetDeclaredIndexes(); - private static string DisplayNameDefault(this ITypeBase type) - => type.ClrType != null && !type.HasSharedClrType - ? type.ClrType.ShortDisplayName() - : type.Name; - /// /// Gets the friendly display name for the given . /// @@ -334,52 +329,56 @@ private static string DisplayNameDefault(this ITypeBase type) /// The display name. [DebuggerStepThrough] public static string DisplayName([NotNull] this ITypeBase type) - => type.FullName() + (type is IEntityType entityType && entityType.HasSharedClrType - ? " (" + entityType.ClrType.ShortDisplayName() + ")" - : ""); - - /// - /// Gets the unique name for the given . - /// - /// The entity type. - /// The display name. - [DebuggerStepThrough] - public static string FullName([NotNull] this ITypeBase type) { - if (!(type is IEntityType entityType) - || !entityType.HasDefiningNavigation()) + if (type.ClrType == null) { - return type.DisplayNameDefault(); + return type.Name; } - var builder = new StringBuilder(); - var path = new Stack(); - var root = entityType; - while (true) + if (!type.HasSharedClrType) { - if (!root.HasDefiningNavigation()) - { - break; - } - - var definingNavigationName = root.DefiningNavigationName; + return type.ClrType.ShortDisplayName(); + } - root = root.DefiningEntityType; - path.Push("#"); - path.Push(definingNavigationName); - path.Push("."); - path.Push(root.DisplayNameDefault()); + var shortName = type.Name; + var hashIndex = shortName.IndexOf("#", StringComparison.Ordinal); + if (hashIndex == -1) + { + return type.Name + " (" + type.ClrType.ShortDisplayName() + ")"; } - if (root != entityType) + var plusIndex = shortName.LastIndexOf("+", StringComparison.Ordinal); + if (plusIndex != -1) { - builder.AppendJoin(path, ""); + shortName = shortName[(plusIndex + 1)..]; + } + else + { + var length = shortName.Length; + var dotIndex = shortName.LastIndexOf(".", hashIndex, hashIndex + 1, StringComparison.Ordinal); + if (dotIndex != -1) + { + dotIndex = shortName.LastIndexOf(".", dotIndex - 1, dotIndex, StringComparison.Ordinal); + if (dotIndex != -1) + { + shortName = shortName[(dotIndex + 1)..]; + } + } } - builder.Append(type.DisplayNameDefault()); - return builder.ToString(); + return shortName == type.Name + ? shortName + " (" + type.ClrType.ShortDisplayName() + ")" + : shortName; } + /// + /// Gets the unique name for the given . + /// + /// The entity type. + /// The full name. + [DebuggerStepThrough] + public static string FullName([NotNull] this ITypeBase type) => type.Name; + /// /// Gets a short name for the given that can be used in other identifiers. /// @@ -394,13 +393,22 @@ public static string ShortName([NotNull] this ITypeBase type) return type.ClrType.ShortDisplayName(); } - var plusIndex = type.Name.LastIndexOf("+", StringComparison.Ordinal); - var dotIndex = type.Name.LastIndexOf(".", StringComparison.Ordinal); - return plusIndex == -1 - ? dotIndex == -1 - ? type.Name - : type.Name.Substring(dotIndex + 1, type.Name.Length - dotIndex - 1) - : type.Name.Substring(plusIndex + 1, type.Name.Length - plusIndex - 1); + var hashIndex = type.Name.LastIndexOf("#", StringComparison.Ordinal); + if (hashIndex == -1) + { + var plusIndex = type.Name.LastIndexOf("+", StringComparison.Ordinal); + if (plusIndex == -1) + { + var dotIndex = type.Name.LastIndexOf(".", StringComparison.Ordinal); + return dotIndex == -1 + ? type.Name + : type.Name[(dotIndex + 1)..]; + } + + return type.Name[(plusIndex + 1)..]; + } + + return type.Name[(hashIndex + 1)..]; } /// @@ -409,6 +417,7 @@ public static string ShortName([NotNull] this ITypeBase type) /// The entity type. /// if this entity type has a defining navigation. [DebuggerStepThrough] + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] public static bool HasDefiningNavigation([NotNull] this IEntityType entityType) => entityType.HasDefiningNavigation(); @@ -578,6 +587,7 @@ public static IEnumerable GetDeclaredReferencingForeignKeys([NotNul /// /// The entity type. /// The defining navigation if one exists or otherwise. + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] public static INavigation? FindDefiningNavigation([NotNull] this IEntityType entityType) { if (!entityType.HasDefiningNavigation()) @@ -585,7 +595,7 @@ public static IEnumerable GetDeclaredReferencingForeignKeys([NotNul return null; } - var definingNavigation = entityType.DefiningEntityType.FindNavigation(entityType.DefiningNavigationName); + var definingNavigation = entityType.DefiningEntityType!.FindNavigation(entityType.DefiningNavigationName!); return definingNavigation?.TargetEntityType == entityType ? definingNavigation : null; } diff --git a/src/EFCore/Extensions/ModelExtensions.cs b/src/EFCore/Extensions/ModelExtensions.cs index ebdf3e752fc..1baf47e426b 100644 --- a/src/EFCore/Extensions/ModelExtensions.cs +++ b/src/EFCore/Extensions/ModelExtensions.cs @@ -75,7 +75,7 @@ public static class ModelExtensions Check.NotNull(definingNavigationName, nameof(definingNavigationName)); Check.NotNull(definingEntityType, nameof(definingEntityType)); - return model.AsModel().FindEntityType( + return ((Model)model).FindEntityType( type, definingNavigationName, definingEntityType.AsEntityType()); @@ -88,7 +88,7 @@ public static class ModelExtensions /// The type of the entity type to find. /// The entity types found. [DebuggerStepThrough] - public static IReadOnlyCollection GetEntityTypes([NotNull] this IModel model, [NotNull] Type type) + public static IEnumerable GetEntityTypes([NotNull] this IModel model, [NotNull] Type type) => ((Model)model).GetEntityTypes(type); /// @@ -98,6 +98,7 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] this IMo /// The name of the entity type to find. /// The entity types found. [DebuggerStepThrough] + [Obsolete("Use GetEntityTypes(Type) or FindEntityType(string)")] public static IReadOnlyCollection GetEntityTypes([NotNull] this IModel model, [NotNull] string name) => ((Model)model).GetEntityTypes(name); @@ -108,9 +109,9 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] this IMo /// The type used to find an entity type a defining navigation. /// if the model contains a corresponding entity type with a defining navigation. [DebuggerStepThrough] + [Obsolete("Use IsShared(Type)")] public static bool HasEntityTypeWithDefiningNavigation([NotNull] this IModel model, [NotNull] Type type) - => Check.NotNull(model, nameof(model)).AsModel() - .HasEntityTypeWithDefiningNavigation(Check.NotNull(type, nameof(type))); + => model.IsShared(type); /// /// Gets a value indicating whether the model contains a corresponding entity type with a defining navigation. @@ -119,9 +120,9 @@ public static bool HasEntityTypeWithDefiningNavigation([NotNull] this IModel mod /// The name used to find an entity type with a defining navigation. /// if the model contains a corresponding entity type with a defining navigation. [DebuggerStepThrough] + [Obsolete("Use FindEntityType(string)?.HasSharedClrType")] public static bool HasEntityTypeWithDefiningNavigation([NotNull] this IModel model, [NotNull] string name) - => Check.NotNull(model, nameof(model)).AsModel() - .HasEntityTypeWithDefiningNavigation(Check.NotNull(name, nameof(name))); + => model.FindEntityType(name)?.HasSharedClrType ?? false; /// /// Gets whether the CLR type is used by shared type entities in the model. diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index 4a8fbc01279..2d4e3913df2 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -390,6 +390,7 @@ public static IMutableForeignKey AddForeignKey( /// /// The entity type. /// The defining navigation if one exists or otherwise. + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] public static IMutableNavigation? FindDefiningNavigation([NotNull] this IMutableEntityType entityType) => (IMutableNavigation?)((IEntityType)entityType).FindDefiningNavigation(); diff --git a/src/EFCore/Extensions/MutableModelExtensions.cs b/src/EFCore/Extensions/MutableModelExtensions.cs index f00b40e1cc9..c893d72f298 100644 --- a/src/EFCore/Extensions/MutableModelExtensions.cs +++ b/src/EFCore/Extensions/MutableModelExtensions.cs @@ -53,7 +53,7 @@ public static class MutableModelExtensions /// The type of the entity type to find. /// The entity types found. [DebuggerStepThrough] - public static IReadOnlyCollection GetEntityTypes([NotNull] this IMutableModel model, [NotNull] Type type) + public static IEnumerable GetEntityTypes([NotNull] this IMutableModel model, [NotNull] Type type) => ((Model)model).GetEntityTypes(type); /// @@ -63,6 +63,7 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] t /// The name of the entity type to find. /// The entity types found. [DebuggerStepThrough] + [Obsolete("Use GetEntityTypes(Type) or FindEntityType(string)")] public static IReadOnlyCollection GetEntityTypes([NotNull] this IMutableModel model, [NotNull] string name) => ((Model)model).GetEntityTypes(name); @@ -81,7 +82,8 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] t } /// - /// Removes an entity type with a defining navigation from the model. + /// Removes an entity type with the given type, defining navigation name + /// and the defining entity type /// /// The model to remove the entity type from. /// The CLR class that is used to represent instances of this entity type. @@ -115,7 +117,8 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] t } /// - /// Removes an entity type with a defining navigation from the model. + /// Removes an entity type with the given type, defining navigation name + /// and the defining entity type /// /// The model to remove the entity type from. /// The name of the entity type to be removed. @@ -133,7 +136,7 @@ public static IReadOnlyCollection GetEntityTypes([NotNull] t Check.NotEmpty(definingNavigationName, nameof(definingNavigationName)); Check.NotNull(definingEntityType, nameof(definingEntityType)); - return ((Model)model).RemoveEntityType(name); + return ((Model)model).RemoveEntityType(name, definingNavigationName, definingEntityType); } /// diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 70c210d924b..377ea2cc491 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -57,7 +57,6 @@ public virtual void Validate(IModel model, IDiagnosticsLogger p.IsCandidateProperty()))) { @@ -240,13 +237,10 @@ var isTargetWeakOrOwned && entityType.GetDerivedTypes().All( dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == actualProperty.GetSimpleMemberName()) == null) - && (!isTargetWeakOrOwned + && (!isTargetSharedOrOwned || (!targetType.Equals(entityType.ClrType) && (!entityType.IsInOwnershipPath(targetType) || (entityType.FindOwnership().PrincipalEntityType.ClrType.Equals(targetType) - && targetSequenceType == null)) - && (!entityType.IsInDefinitionPath(targetType) - || (entityType.DefiningEntityType.ClrType.Equals(targetType) && targetSequenceType == null))))) { if (conventionModel.IsOwned(entityType.ClrType) @@ -257,10 +251,17 @@ var isTargetWeakOrOwned entityType.DisplayName() + "." + actualProperty.Name, targetType.ShortDisplayName())); } + if (model.IsShared(targetType)) + { + throw new InvalidOperationException( + CoreStrings.NonConfiguredNavigationToSharedType(actualProperty.Name, entityType.DisplayName())); + } + throw new InvalidOperationException( CoreStrings.NavigationNotAdded( entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName())); } + // ReSharper restore CheckForReferenceEqualityInstead.3 // ReSharper restore CheckForReferenceEqualityInstead.1 } @@ -550,8 +551,7 @@ private void ValidateClrInheritance( throw new InvalidOperationException(CoreStrings.SharedTypeDerivedType(entityType.DisplayName())); } - if (!entityType.HasDefiningNavigation() - && entityType.FindDeclaredOwnership() == null + if (entityType.FindDeclaredOwnership() == null && entityType.BaseType != null) { var baseClrType = entityType.ClrType?.BaseType; @@ -822,51 +822,11 @@ static bool ContainedInForeignKeyForAllConcreteTypes(IEntityType entityType, IPr /// /// The model to validate. /// The logger to use. + [Obsolete] protected virtual void ValidateDefiningNavigations( [NotNull] IModel model, [NotNull] IDiagnosticsLogger logger) { - Check.NotNull(model, nameof(model)); - - foreach (var entityType in model.GetEntityTypes()) - { - if (entityType.DefiningEntityType != null) - { - if (entityType.FindDefiningNavigation() == null - || (entityType.DefiningEntityType as EntityType)?.Builder == null) - { - throw new InvalidOperationException( - CoreStrings.NoDefiningNavigation( - entityType.DefiningNavigationName, entityType.DisplayName(), entityType.DefiningEntityType.DisplayName())); - } - - var ownership = entityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership); - if (ownership != null) - { - if (ownership.PrincipalToDependent?.Name != entityType.DefiningNavigationName) - { - var ownershipNavigation = ownership.PrincipalToDependent == null - ? "" - : "." + ownership.PrincipalToDependent.Name; - throw new InvalidOperationException( - CoreStrings.NonDefiningOwnership( - ownership.PrincipalEntityType.DisplayName() + ownershipNavigation, - entityType.DefiningNavigationName, - entityType.DisplayName())); - } - - foreach (var otherEntityType in model.GetEntityTypes() - .Where(et => et.ClrType == entityType.ClrType && et != entityType)) - { - if (!otherEntityType.GetForeignKeys().Any(fk => fk.IsOwnership)) - { - throw new InvalidOperationException( - CoreStrings.InconsistentOwnership(entityType.DisplayName(), otherEntityType.DisplayName())); - } - } - } - } - } } /// diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs index e53fea3efad..60560b716d9 100644 --- a/src/EFCore/Internal/EntityFinder.cs +++ b/src/EFCore/Internal/EntityFinder.cs @@ -320,23 +320,19 @@ private static Expression> BuildObjectLambda(IReadOnlyList entityType.FindOwnership() is IForeignKey ownership ? BuildQueryRoot(ownership.PrincipalEntityType, entityType, ownership.PrincipalToDependent.Name) : entityType.HasSharedClrType ? (IQueryable)_setCache.GetOrAddSet(_setSource, entityType.Name, entityType.ClrType) : (IQueryable)_setCache.GetOrAddSet(_setSource, entityType.ClrType); - } - private IQueryable BuildQueryRoot(IEntityType ownerOrDefiningEntityType, IEntityType entityType, string navigationName) + private IQueryable BuildQueryRoot(IEntityType ownerEntityType, IEntityType entityType, string navigationName) { - var queryRoot = BuildQueryRoot(ownerOrDefiningEntityType); - var collectionNavigation = ownerOrDefiningEntityType.FindNavigation(navigationName).IsCollection; + var queryRoot = BuildQueryRoot(ownerEntityType); + var collectionNavigation = ownerEntityType.FindNavigation(navigationName).IsCollection; return (IQueryable)(collectionNavigation ? _selectManyMethod : _selectMethod) - .MakeGenericMethod(ownerOrDefiningEntityType.ClrType, entityType.ClrType) + .MakeGenericMethod(ownerEntityType.ClrType, entityType.ClrType) .Invoke(null, new object[] { queryRoot, navigationName }); } diff --git a/src/EFCore/Internal/InternalDbSet.cs b/src/EFCore/Internal/InternalDbSet.cs index f7bb7b25b26..d7a159fb66b 100644 --- a/src/EFCore/Internal/InternalDbSet.cs +++ b/src/EFCore/Internal/InternalDbSet.cs @@ -76,11 +76,6 @@ public override IEntityType EntityType if (_entityType == null) { - if (_context.Model.HasEntityTypeWithDefiningNavigation(typeof(TEntity))) - { - throw new InvalidOperationException(CoreStrings.InvalidSetTypeWeak(typeof(TEntity).ShortDisplayName())); - } - if (_context.Model.IsShared(typeof(TEntity))) { throw new InvalidOperationException(CoreStrings.InvalidSetSharedType(typeof(TEntity).ShortDisplayName())); diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs index f16ba850548..5f1c1f26abe 100644 --- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs +++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs @@ -164,8 +164,8 @@ public virtual EntityTypeBuilder UsingEntity( var entityTypeBuilder = new EntityTypeBuilder(joinEntityType); - var leftForeignKey = configureLeft(entityTypeBuilder).Metadata; var rightForeignKey = configureRight(entityTypeBuilder).Metadata; + var leftForeignKey = configureLeft(entityTypeBuilder).Metadata; Using(rightForeignKey, leftForeignKey); diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index a3c89d81add..7d656ddda02 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -490,15 +490,16 @@ public virtual EntityTypeBuilder OwnsOne( private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string navigationName) { - InternalForeignKeyBuilder relationship; - using (Builder.Metadata.Model.ConventionDispatcher.DelayConventions()) + IMutableForeignKey foreignKey; + using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions()) { var navigationMember = new MemberIdentity(navigationName); - relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); + var relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); relationship.IsUnique(true, ConfigurationSource.Explicit); + foreignKey = (IMutableForeignKey)batch.Run(relationship.Metadata); } - return new OwnedNavigationBuilder(relationship.Metadata); + return new OwnedNavigationBuilder(foreignKey); } /// @@ -703,15 +704,16 @@ public virtual EntityTypeBuilder OwnsMany( private OwnedNavigationBuilder OwnsManyBuilder(in TypeIdentity ownedType, string navigationName) { - InternalForeignKeyBuilder relationship; - using (Builder.Metadata.Model.ConventionDispatcher.DelayConventions()) + IMutableForeignKey foreignKey; + using (var batch = Builder.Metadata.Model.ConventionDispatcher.DelayConventions()) { var navigationMember = new MemberIdentity(navigationName); - relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); + var relationship = Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); relationship.IsUnique(false, ConfigurationSource.Explicit); + foreignKey = (IMutableForeignKey)batch.Run(relationship.Metadata); } - return new OwnedNavigationBuilder(relationship.Metadata); + return new OwnedNavigationBuilder(foreignKey); } /// @@ -1011,7 +1013,7 @@ protected virtual EntityType FindRelatedEntityType([NotNull] string relatedTypeN /// [EntityFrameworkInternal] protected virtual EntityType FindRelatedEntityType([NotNull] Type relatedType, [CanBeNull] string navigationName) - => (navigationName == null + => (navigationName == null || !Builder.ModelBuilder.Metadata.IsShared(relatedType) ? null : Builder.ModelBuilder.Metadata.FindEntityType(relatedType, navigationName, Builder.Metadata)) ?? Builder.ModelBuilder.Entity(relatedType, ConfigurationSource.Explicit).Metadata; diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs index f2e852294ab..edb4a20e0c8 100644 --- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs @@ -521,15 +521,16 @@ public virtual OwnedNavigationBuilder OwnsOne( private OwnedNavigationBuilder OwnsOneBuilder(in TypeIdentity ownedType, string navigationName) { - InternalForeignKeyBuilder relationship; - using (DependentEntityType.Model.ConventionDispatcher.DelayConventions()) + IMutableForeignKey foreignKey; + using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions()) { var navigationMember = MemberIdentity.Create(navigationName); - relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); + var relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); relationship.IsUnique(true, ConfigurationSource.Explicit); + foreignKey = (IMutableForeignKey)batch.Run(relationship.Metadata); } - return new OwnedNavigationBuilder(relationship.Metadata); + return new OwnedNavigationBuilder(foreignKey); } /// @@ -743,15 +744,16 @@ public virtual OwnedNavigationBuilder OwnsMany( private OwnedNavigationBuilder OwnsManyBuilder(in TypeIdentity ownedType, string navigationName) { - InternalForeignKeyBuilder relationship; - using (DependentEntityType.Model.ConventionDispatcher.DelayConventions()) + IMutableForeignKey foreignKey; + using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions()) { var navigationMember = MemberIdentity.Create(navigationName); - relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); + var relationship = DependentEntityType.Builder.HasOwnership(ownedType, navigationMember, ConfigurationSource.Explicit); relationship.IsUnique(false, ConfigurationSource.Explicit); + foreignKey = (IMutableForeignKey)batch.Run(relationship.Metadata); } - return new OwnedNavigationBuilder(relationship.Metadata); + return new OwnedNavigationBuilder(foreignKey); } /// @@ -819,12 +821,11 @@ public virtual ReferenceNavigationBuilder HasOne( /// The name of the reference navigation property on this entity type that represents the relationship. /// /// An object that can be used to configure the relationship. - public virtual ReferenceNavigationBuilder HasOne( - [NotNull] string navigationName) + public virtual ReferenceNavigationBuilder HasOne([NotNull] string navigationName) { Check.NotEmpty(navigationName, nameof(navigationName)); - return OwnedEntityType.ClrType == null + return OwnedEntityType.ClrType == null || OwnedEntityType.ClrType == Model.DefaultPropertyBagType ? HasOne(navigationName, null) // Path only used by pre 3.0 snapshots : HasOne(OwnedEntityType.GetNavigationMemberInfo(navigationName).GetMemberType(), navigationName); } @@ -881,12 +882,7 @@ public virtual ReferenceNavigationBuilder HasOne( [EntityFrameworkInternal] protected virtual EntityType FindRelatedEntityType([NotNull] string relatedTypeName, [CanBeNull] string navigationName) { - var relatedEntityType = DependentEntityType.FindInDefinitionPath(relatedTypeName); - if (relatedEntityType != null) - { - return relatedEntityType; - } - + EntityType relatedEntityType = null; var model = DependentEntityType.Model; if (navigationName != null) { @@ -897,7 +893,8 @@ protected virtual EntityType FindRelatedEntityType([NotNull] string relatedTypeN && model.GetProductVersion()?.StartsWith("2.", StringComparison.Ordinal) == true) { var owner = DependentEntityType.FindOwnership().PrincipalEntityType; - if (owner.Name == relatedTypeName) + if (owner.Name == relatedTypeName + || owner.ShortName() == relatedTypeName) { relatedEntityType = owner; } @@ -915,15 +912,24 @@ protected virtual EntityType FindRelatedEntityType([NotNull] string relatedTypeN [EntityFrameworkInternal] protected virtual EntityType FindRelatedEntityType([NotNull] Type relatedType, [CanBeNull] string navigationName) { - var relatedEntityType = DependentEntityType.FindInDefinitionPath(relatedType); + var relatedEntityType = (EntityType)DependentEntityType.FindInOwnershipPath(relatedType); if (relatedEntityType != null) { return relatedEntityType; } - if (navigationName != null) + if (Builder.ModelBuilder.Metadata.IsShared(relatedType)) { - relatedEntityType = Builder.ModelBuilder.Metadata.FindEntityType(relatedType, navigationName, DependentEntityType); + if (PrincipalEntityType.HasSharedClrType + && PrincipalEntityType.ClrType == relatedType) + { + return PrincipalEntityType; + } + + if (navigationName != null) + { + relatedEntityType = Builder.ModelBuilder.Metadata.FindEntityType(relatedType, navigationName, DependentEntityType); + } } return relatedEntityType ?? DependentEntityType.Builder.ModelBuilder.Entity(relatedType, ConfigurationSource.Explicit).Metadata; diff --git a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs index d8499abdead..0bd6eca3a73 100644 --- a/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs +++ b/src/EFCore/Metadata/Builders/ReferenceReferenceBuilder.cs @@ -333,7 +333,20 @@ protected virtual EntityType ResolveEntityType([NotNull] string entityTypeName) return (EntityType)DeclaringEntityType; } - return RelatedEntityType.DisplayName() == entityTypeName ? (EntityType)RelatedEntityType : null; + if (RelatedEntityType.DisplayName() == entityTypeName) + { + return (EntityType)RelatedEntityType; + } + + if (DeclaringEntityType.HasSharedClrType + && DeclaringEntityType.ShortName() == entityTypeName) + { + return (EntityType)DeclaringEntityType; + } + + return RelatedEntityType.HasSharedClrType && RelatedEntityType.ShortName() == entityTypeName + ? (EntityType)RelatedEntityType + : null; } /// diff --git a/src/EFCore/Metadata/Conventions/BaseTypeDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/BaseTypeDiscoveryConvention.cs index d3f0fbecc15..9ef7a788ebc 100644 --- a/src/EFCore/Metadata/Conventions/BaseTypeDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/BaseTypeDiscoveryConvention.cs @@ -41,8 +41,7 @@ public virtual void ProcessEntityTypeAdded( var clrType = entityType.ClrType; if (clrType == null || entityType.HasSharedClrType - || entityType.HasDefiningNavigation() - || entityType.Model.FindIsOwnedConfigurationSource(clrType) != null + || entityType.Model.IsOwned(clrType) || entityType.FindDeclaredOwnership() != null) { return; @@ -103,7 +102,6 @@ public virtual void ProcessEntityTypeAdded( } if (!baseEntityType.HasSharedClrType - && !baseEntityType.HasDefiningNavigation() && baseEntityType.FindOwnership() == null) { entityTypeBuilder = entityTypeBuilder.HasBaseType(baseEntityType); diff --git a/src/EFCore/Metadata/Conventions/DerivedTypeDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/DerivedTypeDiscoveryConvention.cs index a21145b1d76..165adcaf53b 100644 --- a/src/EFCore/Metadata/Conventions/DerivedTypeDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/DerivedTypeDiscoveryConvention.cs @@ -39,7 +39,7 @@ public virtual void ProcessEntityTypeAdded( if (clrType == null || entityType.HasSharedClrType || entityType.HasDefiningNavigation() - || entityType.Model.FindIsOwnedConfigurationSource(clrType) != null + || entityType.Model.IsOwned(clrType) || entityType.FindOwnership() != null) { return; @@ -52,7 +52,7 @@ public virtual void ProcessEntityTypeAdded( && directlyDerivedType.HasClrType && !directlyDerivedType.HasSharedClrType && !directlyDerivedType.HasDefiningNavigation() - && model.FindIsOwnedConfigurationSource(directlyDerivedType.ClrType) == null + && !model.IsOwned(directlyDerivedType.ClrType) && directlyDerivedType.FindDeclaredOwnership() == null && ((directlyDerivedType.BaseType == null && clrType.IsAssignableFrom(directlyDerivedType.ClrType)) || (directlyDerivedType.BaseType == entityType.BaseType && FindClosestBaseType(directlyDerivedType) == entityType))) diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs index 0adc7919dd0..1ab6ab3d4b9 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs @@ -163,8 +163,7 @@ private IConventionForeignKeyBuilder DiscoverProperties( } var invertible = true; - if (foreignKey.DeclaringEntityType.DefiningEntityType == foreignKey.PrincipalEntityType - || foreignKey.IsOwnership + if (foreignKey.IsOwnership || foreignKey.DeclaringEntityType.IsKeyless || (!foreignKey.IsUnique && !ConfigurationSource.Convention.Overrides(foreignKey.GetIsUniqueConfigurationSource())) || foreignKey.PrincipalToDependent?.IsCollection == true @@ -177,10 +176,9 @@ private IConventionForeignKeyBuilder DiscoverProperties( invertible = false; } else if (ConfigurationSource.Convention.Overrides(foreignKey.GetPrincipalEndConfigurationSource()) - && (foreignKey.PrincipalEntityType.DefiningEntityType == foreignKey.DeclaringEntityType - || (foreignKey.PrincipalEntityType.FindOwnership() != null + && (foreignKey.PrincipalEntityType.FindOwnership() != null && foreignKey.PrincipalToDependent != null - && foreignKey.DependentToPrincipal == null))) + && foreignKey.DependentToPrincipal == null)) { var invertedRelationshipBuilder = relationshipBuilder.HasEntityTypes( foreignKey.DeclaringEntityType, foreignKey.PrincipalEntityType); @@ -219,7 +217,7 @@ private IConventionForeignKeyBuilder DiscoverProperties( // Try to use PK properties if principal end is not ambiguous if (!foreignKey.IsOwnership && (!ConfigurationSource.Convention.Overrides(foreignKey.GetPrincipalEndConfigurationSource()) - || foreignKey.DeclaringEntityType.DefiningEntityType == foreignKey.PrincipalEntityType)) + || foreignKey.DeclaringEntityType.IsInOwnershipPath(foreignKey.PrincipalEntityType))) { foreignKeyProperties = GetCompatiblePrimaryKeyProperties( foreignKey.DeclaringEntityType, @@ -509,7 +507,10 @@ public virtual void ProcessNavigationAdded( { var navigation = navigationBuilder.Metadata; var newRelationshipBuilder = DiscoverProperties(navigation.ForeignKey.Builder, context); - context.StopProcessingIfChanged(newRelationshipBuilder?.Metadata.GetNavigation(navigation.IsOnDependent)?.Builder); + if (newRelationshipBuilder != null) + { + context.StopProcessingIfChanged(newRelationshipBuilder.Metadata.GetNavigation(navigation.IsOnDependent)?.Builder); + } } /// @@ -650,7 +651,11 @@ public virtual void ProcessForeignKeyRequirednessChanged( } var newForeignKey = batch.Run(DiscoverProperties(relationshipBuilder, context).Metadata); - context.StopProcessingIfChanged(newForeignKey?.IsRequired); + if (newForeignKey != relationshipBuilder.Metadata + || newForeignKey.IsRequired != isRequired) + { + context.StopProcessing(); + } } /// @@ -674,7 +679,10 @@ public virtual void ProcessForeignKeyPropertiesChanged( ProcessForeignKey(relationshipBuilder, context); - context.StopProcessingIfChanged(relationshipBuilder?.Metadata.Properties); + if (relationshipBuilder.Metadata.Builder != null) + { + context.StopProcessingIfChanged(relationshipBuilder.Metadata.Properties); + } } /// diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index d4203620973..4098e912998 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -79,7 +79,6 @@ public virtual ConventionSet CreateConventionSet() conventionSet.EntityTypeIgnoredConventions.Add(relationshipDiscoveryConvention); var discriminatorConvention = new DiscriminatorConvention(Dependencies); - conventionSet.EntityTypeRemovedConventions.Add(new OwnedTypesConvention(Dependencies)); conventionSet.EntityTypeRemovedConventions.Add(inversePropertyAttributeConvention); conventionSet.EntityTypeRemovedConventions.Add(discriminatorConvention); diff --git a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs index 5102fb88201..b4f37a5be59 100644 --- a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs @@ -242,21 +242,17 @@ private IConventionForeignKeyBuilder ConfigureInverseNavigation( return null; } - if (entityType.DefiningEntityType != null - && entityType.DefiningEntityType == targetEntityTypeBuilder.Metadata - && entityType.DefiningNavigationName != inverseNavigationPropertyInfo.GetSimpleMemberName()) - { - Dependencies.Logger.NonDefiningInverseNavigationWarning( - entityType, navigationMemberInfo, - targetEntityTypeBuilder.Metadata, inverseNavigationPropertyInfo, - entityType.DefiningEntityType.GetRuntimeProperties()[entityType.DefiningNavigationName]); - - return null; - } - - if (entityType.Model.FindIsOwnedConfigurationSource(entityType.ClrType) != null + if (entityType.Model.IsOwned(entityType.ClrType) && !entityType.IsInOwnershipPath(targetEntityTypeBuilder.Metadata)) { + if (navigationMemberInfo.DeclaringType != entityType.ClrType + && (entityType.Model.GetEntityTypes(navigationMemberInfo.DeclaringType).Any() + || (navigationMemberInfo.DeclaringType != entityType.ClrType.BaseType + && entityType.Model.GetEntityTypes(entityType.ClrType.BaseType).Any()))) + { + return null; + } + return targetEntityTypeBuilder.HasOwnership( entityTypeBuilder.Metadata.ClrType, inverseNavigationPropertyInfo, @@ -328,9 +324,7 @@ public override void ProcessNavigationAdded( { var navigation = navigationBuilder.Metadata; var foreignKey = navigation.ForeignKey; - if (foreignKey.DeclaringEntityType.HasDefiningNavigation() - || foreignKey.DeclaringEntityType.IsOwned() - || foreignKey.PrincipalEntityType.HasDefiningNavigation() + if (foreignKey.DeclaringEntityType.IsOwned() || foreignKey.PrincipalEntityType.IsOwned()) { return; diff --git a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs index e0ff87af24a..ac26528729a 100644 --- a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs @@ -70,17 +70,16 @@ protected virtual void TryConfigurePrimaryKey([NotNull] IConventionEntityTypeBui } List keyProperties = null; - var definingFk = entityType.FindDefiningNavigation()?.ForeignKey - ?? entityType.FindOwnership(); - if (definingFk != null - && definingFk.DeclaringEntityType != entityType) + var ownership = entityType.FindOwnership(); + if (ownership != null + && ownership.DeclaringEntityType != entityType) { - definingFk = null; + ownership = null; } - if (definingFk?.IsUnique == true) + if (ownership?.IsUnique == true) { - keyProperties = definingFk.Properties.ToList(); + keyProperties = ownership.Properties.ToList(); } if (keyProperties == null) @@ -96,16 +95,16 @@ protected virtual void TryConfigurePrimaryKey([NotNull] IConventionEntityTypeBui } } - if (definingFk?.IsUnique == false) + if (ownership?.IsUnique == false) { if (keyProperties.Count == 0 - || definingFk.Properties.Contains(keyProperties.First())) + || ownership.Properties.Contains(keyProperties.First())) { var primaryKey = entityType.FindPrimaryKey(); var shadowProperty = primaryKey?.Properties.Last(); if (shadowProperty == null || primaryKey.Properties.Count == 1 - || definingFk.Properties.Contains(shadowProperty)) + || ownership.Properties.Contains(shadowProperty)) { shadowProperty = entityTypeBuilder.CreateUniqueProperty(typeof(int), "Id", required: true).Metadata; } @@ -116,7 +115,7 @@ protected virtual void TryConfigurePrimaryKey([NotNull] IConventionEntityTypeBui var extraProperty = keyProperties[0]; keyProperties.RemoveAt(0); - keyProperties.AddRange(definingFk.Properties); + keyProperties.AddRange(ownership.Properties); keyProperties.Add(extraProperty); } diff --git a/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs b/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs index 20320625645..e70b9ee815f 100644 --- a/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs +++ b/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs @@ -73,8 +73,7 @@ private IReadOnlyList GetRoots(IConventionModel model, Co private void RemoveNavigationlessForeignKeys(IConventionModelBuilder modelBuilder) { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes() - .Where(e => !((EntityType)e).IsImplicitlyCreatedJoinEntityType)) + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { foreach (var foreignKey in entityType.GetDeclaredForeignKeys().ToList()) { diff --git a/src/EFCore/Metadata/Conventions/OwnedTypesConvention.cs b/src/EFCore/Metadata/Conventions/OwnedTypesConvention.cs index 65a947ecb10..e7e909da58e 100644 --- a/src/EFCore/Metadata/Conventions/OwnedTypesConvention.cs +++ b/src/EFCore/Metadata/Conventions/OwnedTypesConvention.cs @@ -1,11 +1,10 @@ // 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 System; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions { @@ -13,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions /// A convention that configures owned entity types with defining navigation as owned entity types /// without defining navigation if there's only one navigation of this type. /// + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] public class OwnedTypesConvention : IEntityTypeRemovedConvention { /// @@ -40,22 +40,6 @@ public virtual void ProcessEntityTypeRemoved( IConventionEntityType entityType, IConventionContext context) { - if (!entityType.HasDefiningNavigation()) - { - return; - } - - var entityTypes = modelBuilder.Metadata.GetEntityTypes(entityType.Name); - var otherEntityType = entityTypes.FirstOrDefault(); - if (otherEntityType?.HasDefiningNavigation() == true - && entityTypes.Count == 1 - && otherEntityType.FindOwnership() is ForeignKey ownership) - { - using (context.DelayConventions()) - { - InternalEntityTypeBuilder.DetachRelationship(ownership).Attach(ownership.PrincipalEntityType.Builder); - } - } } } } diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index 1935365814b..da712be3209 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -69,7 +69,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent var relationshipCandidates = new Dictionary(); var ownership = entityTypeBuilder.Metadata.FindOwnership(); if (ownership == null - && model.FindIsOwnedConfigurationSource(entityTypeBuilder.Metadata.ClrType) != null) + && model.IsOwned(entityTypeBuilder.Metadata.ClrType)) { return relationshipCandidates.Values.ToList(); } @@ -80,7 +80,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent var targetClrType = candidateTuple.Value; if (!IsCandidateNavigationProperty(entityTypeBuilder, navigationPropertyInfo.GetSimpleMemberName(), navigationPropertyInfo) - || (model.FindIsOwnedConfigurationSource(targetClrType) != null + || (model.IsOwned(targetClrType) && HasDeclaredAmbiguousNavigationsTo(entityType, targetClrType))) { continue; @@ -106,8 +106,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent { var targetType = relationshipCandidate.TargetTypeBuilder.Metadata; if (targetType.Builder != null - && targetType.HasDefiningNavigation() - && targetType.DefiningEntityType.FindNavigation(targetType.DefiningNavigationName) == null) + && IsImplicitlyCreatedUnusedSharedType(targetType)) { targetType.Builder.ModelBuilder.HasNoEntityType(targetType); } @@ -116,7 +115,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent return Array.Empty(); } - if (model.FindIsOwnedConfigurationSource(targetClrType) == null) + if (!model.IsOwned(targetClrType)) { var targetOwnership = candidateTargetEntityType.FindOwnership(); if (targetOwnership != null @@ -156,19 +155,15 @@ private IReadOnlyList FindRelationshipCandidates(IConvent if ((inverseTargetType != entityType.ClrType && (!inverseTargetType.IsAssignableFrom(entityType.ClrType) - || (model.FindIsOwnedConfigurationSource(targetClrType) == null + || (!model.IsOwned(targetClrType) && !candidateTargetEntityType.IsInOwnershipPath(entityType)))) || navigationPropertyInfo.IsSameAs(inversePropertyInfo) || (ownership != null && !candidateTargetEntityType.IsInOwnershipPath(entityType) && (candidateTargetEntityType.IsOwned() - || model.FindIsOwnedConfigurationSource(targetClrType) == null) + || !model.IsOwned(targetClrType)) && (ownership.PrincipalEntityType != candidateTargetEntityType || ownership.PrincipalToDependent?.Name != inversePropertyInfo.GetSimpleMemberName())) - || (entityType.HasDefiningNavigation() - && !candidateTargetEntityType.IsInDefinitionPath(entityType.ClrType) - && (entityType.DefiningEntityType != candidateTargetEntityType - || entityType.DefiningNavigationName != inversePropertyInfo.GetSimpleMemberName())) || !IsCandidateNavigationProperty( candidateTargetEntityTypeBuilder, inversePropertyInfo.GetSimpleMemberName(), inversePropertyInfo)) { @@ -358,7 +353,7 @@ private static IReadOnlyList RemoveIncompatibleWithExisti { filteredRelationshipCandidates.Add(relationshipCandidate); } - else if (IsCandidateUnusedOwnedType(relationshipCandidate.TargetTypeBuilder.Metadata) + else if (IsImplicitlyCreatedUnusedSharedType(relationshipCandidate.TargetTypeBuilder.Metadata) && filteredRelationshipCandidates.All( c => c.TargetTypeBuilder.Metadata != relationshipCandidate.TargetTypeBuilder.Metadata)) { @@ -505,7 +500,7 @@ private static IReadOnlyList RemoveSingleSidedBaseNavigat { filteredRelationshipCandidates.Add(relationshipCandidate); } - else if (IsCandidateUnusedOwnedType(relationshipCandidate.TargetTypeBuilder.Metadata) + else if (IsImplicitlyCreatedUnusedSharedType(relationshipCandidate.TargetTypeBuilder.Metadata) && filteredRelationshipCandidates.All( c => c.TargetTypeBuilder.Metadata != relationshipCandidate.TargetTypeBuilder.Metadata)) { @@ -624,8 +619,10 @@ private void CreateRelationships( continue; } - var targetOwned = targetEntityType.Model.IsOwned(targetEntityType.ClrType) - && !entityType.IsInOwnershipPath(targetEntityType); + var targetOwned = (!entityType.IsInOwnershipPath(targetEntityType) + && (targetEntityType.Model.IsOwned(targetEntityType.ClrType) + || (targetEntityType.HasSharedClrType + && targetEntityType.Model.GetEntityTypes(targetEntityType.ClrType).Any(e => e.IsOwned())))); var inverse = relationshipCandidate.InverseProperties.SingleOrDefault(); if (inverse == null) @@ -711,8 +708,7 @@ private void CreateRelationships( foreach (var unusedEntityType in unusedEntityTypes) { - if (IsCandidateUnusedOwnedType(unusedEntityType) - && unusedEntityType.DefiningEntityType.FindNavigation(unusedEntityType.DefiningNavigationName) == null) + if (IsImplicitlyCreatedUnusedSharedType(unusedEntityType)) { entityTypeBuilder.ModelBuilder.HasNoEntityType(unusedEntityType); } @@ -1008,8 +1004,11 @@ private static void SetNavigationCandidates( ImmutableSortedDictionary navigationCandidates) => entityTypeBuilder.HasAnnotation(CoreAnnotationNames.NavigationCandidates, navigationCandidates); - private static bool IsCandidateUnusedOwnedType(IConventionEntityType entityType) - => entityType.HasDefiningNavigation() && !entityType.GetForeignKeys().Any(); + private static bool IsImplicitlyCreatedUnusedSharedType(IConventionEntityType entityType) + => 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/EntityTypeFullNameComparer.cs b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs index 02985a236ba..57f03e7ddb1 100644 --- a/src/EFCore/Metadata/EntityTypeFullNameComparer.cs +++ b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs @@ -52,42 +52,7 @@ public int Compare(IEntityType? x, IEntityType? y) return 1; } - var result = StringComparer.Ordinal.Compare(x.Name, y.Name); - if (result != 0) - { - return result; - } - - while (true) - { - var xDefiningNavigationName = x.DefiningNavigationName; - var yDefiningNavigationName = y.DefiningNavigationName; - - if (xDefiningNavigationName == null - && yDefiningNavigationName == null) - { - return StringComparer.Ordinal.Compare(x.Name, y.Name); - } - - if (xDefiningNavigationName == null) - { - return -1; - } - - if (yDefiningNavigationName == null) - { - return 1; - } - - result = StringComparer.Ordinal.Compare(xDefiningNavigationName, yDefiningNavigationName); - if (result != 0) - { - return result; - } - - x = x.DefiningEntityType!; - y = y.DefiningEntityType!; - } + return StringComparer.Ordinal.Compare(x.Name, y.Name); } /// @@ -105,19 +70,6 @@ public bool Equals(IEntityType? x, IEntityType? y) /// The for which a hash code is to be returned. /// A hash code for the specified object. public int GetHashCode(IEntityType obj) - { - var hash = new HashCode(); - while (true) - { - hash.Add(obj.Name, StringComparer.Ordinal); - if (!obj.HasDefiningNavigation()) - { - return hash.ToHashCode(); - } - - hash.Add(obj.DefiningNavigationName, StringComparer.Ordinal); - obj = obj.DefiningEntityType; - } - } + => obj.Name.GetHashCode(); } } diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index fed58c0705d..f61d67b9969 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -47,7 +47,8 @@ public interface IConventionEntityType : IEntityType, IConventionTypeBase /// /// Gets the defining entity type. /// - new IConventionEntityType? DefiningEntityType { get; } + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] + new IConventionEntityType? DefiningEntityType => null; /// /// Gets a value indicating whether the entity type has no keys. diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 6d702168d9b..dd85398ed9d 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -1,12 +1,12 @@ // 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.Collections.Generic; using System.Linq; using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; -using CA = System.Diagnostics.CodeAnalysis; #nullable enable @@ -25,28 +25,21 @@ public interface IEntityType : ITypeBase /// /// Gets the name of the defining navigation. /// - string? DefiningNavigationName { get; } + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] + string? DefiningNavigationName => null; /// /// Gets the defining entity type. /// - IEntityType? DefiningEntityType { get; } + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] + IEntityType? DefiningEntityType => null; /// /// Gets a value indicating whether this entity type has a defining navigation. /// /// if this entity type has a defining navigation. - [CA.MemberNotNullWhen(true, nameof(DefiningNavigationName), nameof(DefiningEntityType))] - public bool HasDefiningNavigation() - { - if (DefiningEntityType != null) - { - Check.DebugAssert(DefiningNavigationName != null, - $"{nameof(DefiningEntityType)} is non-null but {nameof(DefiningNavigationName)} is null"); - return true; - } - return false; - } + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] + public bool HasDefiningNavigation() => HasSharedClrType; /// /// Gets primary key for this entity type. Returns if no primary key is defined. diff --git a/src/EFCore/Metadata/IModel.cs b/src/EFCore/Metadata/IModel.cs index 34e293d7526..c1fef13bd23 100644 --- a/src/EFCore/Metadata/IModel.cs +++ b/src/EFCore/Metadata/IModel.cs @@ -33,22 +33,22 @@ public interface IModel : IAnnotatable IEnumerable GetEntityTypes(); /// - /// Gets the entity type with the given name. Returns null if no entity type with the given name is found + /// Gets the entity type with the given name. Returns if no entity type with the given name is found /// or the given CLR type is being used by shared type entity type /// or the entity type has a defining navigation. /// /// The name of the entity type to find. - /// The entity type, or null if none are found. + /// The entity type, or if none are found. IEntityType? FindEntityType([NotNull] string name); /// - /// Gets the entity type for the given name, defining navigation name - /// and the defining entity type. Returns null if no matching entity type is found. + /// Gets the entity type for the given base name, defining navigation name + /// and the defining entity type. Returns if no matching entity type is found. /// /// The name of the entity type to find. /// The defining navigation of the entity type to find. /// The defining entity type of the entity type to find. - /// The entity type, or null if none are found. + /// The entity type, or if none are found. IEntityType? FindEntityType( [NotNull] string name, [NotNull] string definingNavigationName, diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index 94a1a0b60f2..b7c6511ced2 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -36,7 +36,8 @@ public interface IMutableEntityType : IEntityType, IMutableTypeBase /// /// Gets the defining entity type. /// - new IMutableEntityType? DefiningEntityType { get; } + [Obsolete("Entity types with defining navigations have been replaced by shared-type entity types")] + new IMutableEntityType? DefiningEntityType => null; /// /// Gets or sets a value indicating whether the entity type has no keys. diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index e91823d1479..10c11937d3e 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -17,7 +17,6 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; -using CA = System.Diagnostics.CodeAnalysis; #nullable enable @@ -141,42 +140,6 @@ public EntityType([NotNull] string name, [NotNull] Type type, [NotNull] Model mo Builder = new InternalEntityTypeBuilder(this, model.Builder); } - /// - /// 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 EntityType( - [NotNull] string name, - [NotNull] Model model, - [NotNull] string definingNavigationName, - [NotNull] EntityType definingEntityType, - ConfigurationSource configurationSource) - : this(name, model, configurationSource) - { - DefiningNavigationName = definingNavigationName; - DefiningEntityType = definingEntityType; - } - - /// - /// 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 EntityType( - [NotNull] Type type, - [NotNull] Model model, - [NotNull] string definingNavigationName, - [NotNull] EntityType definingEntityType, - ConfigurationSource configurationSource) - : this(type, model, configurationSource) - { - DefiningNavigationName = definingNavigationName; - DefiningEntityType = definingEntityType; - } - /// /// 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 @@ -212,38 +175,6 @@ public virtual bool IsKeyless set => SetIsKeyless(value, ConfigurationSource.Explicit); } - /// - /// 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 string? DefiningNavigationName { get; } - - /// - /// 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? DefiningEntityType { get; } - - /// - /// Gets a value indicating whether this entity type has a defining navigation. - /// - /// if this entity type has a defining navigation. - [CA.MemberNotNullWhen(true, nameof(DefiningNavigationName), nameof(DefiningEntityType))] - public virtual bool HasDefiningNavigation() - { - if (DefiningEntityType != null) - { - Check.DebugAssert(DefiningNavigationName != null, - $"{nameof(DefiningEntityType)} is non-null but {nameof(DefiningNavigationName)} is null"); - return true; - } - return false; - } - /// /// 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 @@ -322,11 +253,6 @@ public virtual void UpdateIsKeylessConfigurationSource(ConfigurationSource confi return newBaseType; } - if (HasDefiningNavigation()) - { - throw new InvalidOperationException(CoreStrings.WeakDerivedType(this.DisplayName())); - } - var originalBaseType = _baseType; _baseType?._directlyDerivedTypes.Remove(this); @@ -348,11 +274,6 @@ public virtual void UpdateIsKeylessConfigurationSource(ConfigurationSource confi this.DisplayName(), newBaseType.DisplayName(), ClrType.ShortDisplayName(), newBaseType.ClrType.ShortDisplayName())); } - - if (newBaseType.HasDefiningNavigation()) - { - throw new InvalidOperationException(CoreStrings.WeakBaseType(this.DisplayName(), newBaseType.DisplayName())); - } } if (!HasClrType @@ -1547,7 +1468,8 @@ private Navigation AddNavigation(MemberIdentity navigationMember, ForeignKey for memberInfo = ClrType?.GetMembersInHierarchy(name).FirstOrDefault(); } - if (ClrType != null) + if (ClrType != null + && ClrType != Model.DefaultPropertyBagType) { Navigation.IsCompatible( name, @@ -3423,42 +3345,6 @@ IConventionModel IConventionEntityType.Model get => BaseType; } - /// - /// 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. - /// - IEntityType? IEntityType.DefiningEntityType - { - [DebuggerStepThrough] - get => DefiningEntityType; - } - - /// - /// 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. - /// - IMutableEntityType? IMutableEntityType.DefiningEntityType - { - [DebuggerStepThrough] - get => DefiningEntityType; - } - - /// - /// 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. - /// - IConventionEntityType? IConventionEntityType.DefiningEntityType - { - [DebuggerStepThrough] - get => DefiningEntityType; - } - /// /// 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/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index d7423590aa2..1a4544b10bd 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -62,88 +63,13 @@ public static MemberInfo GetNavigationMemberInfo( /// 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 static IEntityType? FindInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) - { - var root = entityType; - while (root != null) - { - if (root.ClrType == targetType) - { - return root; - } - - root = root.DefiningEntityType; - } - - 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 static EntityType? FindInDefinitionPath([NotNull] this EntityType entityType, [NotNull] Type targetType) - => (EntityType?)((IEntityType)entityType).FindInDefinitionPath(targetType); - - /// - /// 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 static IEntityType? FindInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] string targetTypeName) + public static IEntityType? FindInOwnershipPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) { - var root = entityType; - while (root != null) + if (entityType.ClrType == targetType) { - if (root.Name == targetTypeName) - { - return root; - } - - root = root.DefiningEntityType; + return entityType; } - 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 static EntityType? FindInDefinitionPath([NotNull] this EntityType entityType, [NotNull] string targetTypeName) - => (EntityType?)((IEntityType)entityType).FindInDefinitionPath(targetTypeName); - - /// - /// 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 static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) - => entityType.FindInDefinitionPath(targetType) != 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 static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [NotNull] string targetTypeName) - => entityType.FindInDefinitionPath(targetTypeName) != 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 static IEntityType? FindInOwnershipPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) - { var owner = entityType; while (true) { @@ -170,6 +96,16 @@ public static bool IsInDefinitionPath([NotNull] this IEntityType entityType, [No public static bool IsInOwnershipPath([NotNull] this IEntityType entityType, [NotNull] Type targetType) => entityType.FindInOwnershipPath(targetType) != 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. + /// + [DebuggerStepThrough] + public static string GetOwnedName([NotNull] this ITypeBase type, [NotNull] string simpleName, [NotNull] string ownershipNavigation) + => type.Name + "." + ownershipNavigation + "#" + simpleName; + /// /// 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/ForeignKey.cs b/src/EFCore/Metadata/Internal/ForeignKey.cs index 2635b7ac777..46c3c720314 100644 --- a/src/EFCore/Metadata/Internal/ForeignKey.cs +++ b/src/EFCore/Metadata/Internal/ForeignKey.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Utilities; +using CA = System.Diagnostics.CodeAnalysis; #nullable enable @@ -529,8 +530,10 @@ public virtual bool IsUnique } if (unique.HasValue - && PrincipalEntityType.ClrType != null - && DeclaringEntityType.ClrType != null + && PrincipalEntityType.HasClrType + && DeclaringEntityType.HasClrType + && PrincipalEntityType.ClrType != Model.DefaultPropertyBagType + && DeclaringEntityType.ClrType != Model.DefaultPropertyBagType && PrincipalToDependent != null) { if (!Internal.Navigation.IsCompatible( @@ -540,7 +543,6 @@ public virtual bool IsUnique !unique, shouldThrow: false)) { - // TODO-NULLABLE: Bug if PropertyInfo is null (FieldInfo?) throw new InvalidOperationException( CoreStrings.UnableToSetIsUnique( unique.Value, @@ -748,6 +750,7 @@ public virtual void UpdateDeleteBehaviorConfigurationSource(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. /// + [CA.MemberNotNullWhen(true, nameof(PrincipalToDependent))] public virtual bool IsOwnership { get => _isOwnership ?? DefaultIsOwnership; @@ -1272,18 +1275,6 @@ public static bool AreCompatible( Check.NotNull(principalEntityType, nameof(principalEntityType)); Check.NotNull(dependentEntityType, nameof(dependentEntityType)); - if (principalEntityType.HasDefiningNavigation() - && principalEntityType == dependentEntityType) - { - if (shouldThrow) - { - throw new InvalidOperationException( - CoreStrings.ForeignKeySelfReferencingDependentEntityType(dependentEntityType.DisplayName())); - } - - return false; - } - if (navigationToPrincipal != null && !Internal.Navigation.IsCompatible( navigationToPrincipal, diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 5a7d97a9934..d8b34b535cc 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -2022,7 +2022,7 @@ public static RelationshipSnapshot DetachRelationship([NotNull] ForeignKey forei /// 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 static RelationshipSnapshot DetachRelationship([NotNull] ForeignKey foreignKey, bool includeDefinedType) + public static RelationshipSnapshot DetachRelationship([NotNull] ForeignKey foreignKey, bool includeOwnedSharedType) { var detachedBuilder = foreignKey.Builder; var referencingSkipNavigations = foreignKey.ReferencingSkipNavigations? @@ -2031,19 +2031,17 @@ public static RelationshipSnapshot DetachRelationship([NotNull] ForeignKey forei .HasNoRelationship(foreignKey, foreignKey.GetConfigurationSource()); Check.DebugAssert(relationshipConfigurationSource != null, "relationshipConfigurationSource is null"); - EntityType.Snapshot definedSnapshot = null; - if (includeDefinedType) + EntityType.Snapshot ownedSnapshot = null; + var dependentEntityType = foreignKey.DeclaringEntityType; + if (includeOwnedSharedType + && foreignKey.IsOwnership + && dependentEntityType.HasSharedClrType) { - var dependentEntityType = foreignKey.DeclaringEntityType; - if (dependentEntityType.DefiningEntityType == foreignKey.PrincipalEntityType - && dependentEntityType.DefiningNavigationName == foreignKey.PrincipalToDependent?.Name) - { - definedSnapshot = DetachAllMembers(dependentEntityType); - dependentEntityType.Model.Builder.HasNoEntityType(dependentEntityType, ConfigurationSource.Explicit); - } + ownedSnapshot = DetachAllMembers(dependentEntityType); + dependentEntityType.Model.Builder.HasNoEntityType(dependentEntityType, ConfigurationSource.Explicit); } - return new RelationshipSnapshot(detachedBuilder, definedSnapshot, referencingSkipNavigations); + return new RelationshipSnapshot(detachedBuilder, ownedSnapshot, referencingSkipNavigations); } /// @@ -2105,12 +2103,6 @@ public static EntityType.Snapshot DetachAllMembers([NotNull] EntityType entityTy return null; } - if (entityType.HasDefiningNavigation()) - { - entityType.Model.AddDetachedEntityType( - entityType.Name, entityType.DefiningNavigationName, entityType.DefiningEntityType.Name); - } - List detachedRelationships = null; foreach (var relationshipToBeDetached in entityType.GetDeclaredForeignKeys().ToList()) { @@ -2119,7 +2111,7 @@ public static EntityType.Snapshot DetachAllMembers([NotNull] EntityType entityTy detachedRelationships = new List(); } - var detachedRelationship = DetachRelationship(relationshipToBeDetached); + var detachedRelationship = DetachRelationship(relationshipToBeDetached, false); if (detachedRelationship.Relationship.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation) || relationshipToBeDetached.IsOwnership) { @@ -2919,7 +2911,8 @@ private InternalForeignKeyBuilder HasRelationship( else { if (setTargetAsPrincipal == true - || targetEntityType.DefiningEntityType != Metadata) + || (setTargetAsPrincipal == null + && !targetEntityType.IsInOwnershipPath(Metadata))) { newRelationship = CreateForeignKey( targetEntityType.Builder, @@ -3163,8 +3156,7 @@ public virtual InternalForeignKeyBuilder HasOwnership( in TypeIdentity typeIdentity, MemberIdentity navigation, ConfigurationSource configurationSource) - => HasOwnership( - typeIdentity, navigation, inverse: null, configurationSource); + => HasOwnership(typeIdentity, navigation, inverse: null, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3202,175 +3194,29 @@ public virtual InternalForeignKeyBuilder HasOwnership( private InternalForeignKeyBuilder HasOwnership( in TypeIdentity targetEntityType, - MemberIdentity navigation, - MemberIdentity? inverse, + in MemberIdentity navigation, + in MemberIdentity? inverse, ConfigurationSource configurationSource) { - InternalEntityTypeBuilder ownedEntityType; + InternalEntityTypeBuilder ownedEntityTypeBuilder; InternalForeignKeyBuilder relationship; using (var batch = Metadata.Model.ConventionDispatcher.DelayConventions()) { - var existingNavigation = Metadata.FindNavigation(navigation.Name); - if (existingNavigation != null) - { - if (existingNavigation.TargetEntityType.Name == targetEntityType.Name) - { - var existingOwnedEntityType = existingNavigation.ForeignKey.DeclaringEntityType; - // Upgrade configurationSource for existing entity type - if (existingOwnedEntityType.HasDefiningNavigation()) - { - if (targetEntityType.IsNamed) - { - ModelBuilder.Entity( - targetEntityType.Name, - existingOwnedEntityType.DefiningNavigationName, - existingOwnedEntityType.DefiningEntityType, - configurationSource); - } - else - { - ModelBuilder.Entity( - targetEntityType.Type, - existingOwnedEntityType.DefiningNavigationName, - existingOwnedEntityType.DefiningEntityType, - configurationSource); - } - } - else - { - if (targetEntityType.IsNamed) - { - if (targetEntityType.Type != null) - { - ModelBuilder.SharedTypeEntity( - targetEntityType.Name, targetEntityType.Type, configurationSource, shouldBeOwned: true); - } - else - { - ModelBuilder.Entity(targetEntityType.Name, configurationSource, shouldBeOwned: true); - } - } - else - { - ModelBuilder.Entity(targetEntityType.Type, configurationSource, shouldBeOwned: true); - } - } + var ownership = Metadata.FindOwnership(); + ownedEntityTypeBuilder = GetTargetEntityTypeBuilder(targetEntityType, navigation, configurationSource, targetShouldBeOwned: true); - var ownershipBuilder = existingNavigation.ForeignKey.Builder; - ownershipBuilder = ownershipBuilder - .HasEntityTypes( - Metadata, ownershipBuilder.Metadata.FindNavigationsFromInHierarchy(Metadata).Single().TargetEntityType, - configurationSource) - ?.IsRequired(true, configurationSource) - ?.HasNavigations(inverse, navigation, configurationSource) - ?.IsOwnership(true, configurationSource); - - return ownershipBuilder == null ? null : batch.Run(ownershipBuilder); - } + var principalBuilder = Metadata.Builder + ?? ownership?.PrincipalEntityType.FindNavigation(ownership.PrincipalToDependent.Name)?.TargetEntityType.Builder; - if (existingNavigation.ForeignKey.DeclaringEntityType.Builder - .HasNoRelationship(existingNavigation.ForeignKey, configurationSource) == null) - { - return null; - } - } - - var principalBuilder = this; - var targetTypeName = targetEntityType.Name; - var targetType = targetEntityType.Type; - if (targetType == null) + if (ownedEntityTypeBuilder == null + || principalBuilder == null) { - var memberType = existingNavigation?.GetIdentifyingMemberInfo()?.GetMemberType(); - if (memberType != null) - { - targetType = memberType.TryGetSequenceType() ?? memberType; - } - } - - ownedEntityType = targetEntityType.IsNamed - ? ModelBuilder.Metadata.FindEntityType(targetTypeName)?.Builder - : ModelBuilder.Metadata.FindEntityType(targetType)?.Builder; - if (ownedEntityType == null) - { - if (Metadata.Model.EntityTypeShouldHaveDefiningNavigation(targetTypeName)) - { - if (!configurationSource.Overrides(ConfigurationSource.Explicit) - && (targetType == null - ? Metadata.IsInDefinitionPath(targetTypeName) - : Metadata.IsInDefinitionPath(targetType))) - { - return null; - } - - ownedEntityType = targetType == null - ? ModelBuilder.Entity(targetTypeName, navigation.Name, Metadata, configurationSource) - : ModelBuilder.Entity(targetType, navigation.Name, Metadata, configurationSource); - } - else - { - if (ModelBuilder.IsIgnored(targetTypeName, configurationSource)) - { - return null; - } - - ModelBuilder.Metadata.RemoveIgnored(targetTypeName); - - ownedEntityType = targetEntityType.IsNamed - ? targetType == null - ? ModelBuilder.Entity(targetTypeName, configurationSource, shouldBeOwned: true) - : ModelBuilder.SharedTypeEntity(targetTypeName, targetType, configurationSource, shouldBeOwned: true) - : ModelBuilder.Entity(targetType, configurationSource, shouldBeOwned: true); - } - - if (ownedEntityType == null) - { - return null; - } - } - else - { - var otherOwnership = ownedEntityType.Metadata.FindDeclaredOwnership(); - if (otherOwnership != null) - { - if (!configurationSource.Overrides(ConfigurationSource.Explicit) - && (targetEntityType.IsNamed - ? Metadata.IsInDefinitionPath(targetTypeName) - : Metadata.IsInDefinitionPath(targetType))) - { - return null; - } - - if (targetEntityType.IsNamed - && targetType != null) - { - if (configurationSource == ConfigurationSource.Explicit) - { - throw new InvalidOperationException( - CoreStrings.ClashingNamedOwnedType( - targetTypeName, Metadata.DisplayName(), navigation.Name)); - } - - return null; - } - - var newOtherOwnership = otherOwnership.Builder.AddToDeclaringTypeDefinition(configurationSource); - if (newOtherOwnership == null) - { - return null; - } - - if (otherOwnership.DeclaringEntityType == Metadata) - { - principalBuilder = newOtherOwnership.Metadata.DeclaringEntityType.Builder; - } - - ownedEntityType = targetType == null - ? ModelBuilder.Entity(targetTypeName, navigation.Name, principalBuilder.Metadata, configurationSource) - : ModelBuilder.Entity(targetType, navigation.Name, principalBuilder.Metadata, configurationSource); - } + Check.DebugAssert(configurationSource != ConfigurationSource.Explicit, + $"Adding {Metadata.ShortName()}.{navigation.Name} ownership failed because one of the related types doesn't exist."); + return null; } - relationship = ownedEntityType.HasRelationship( + relationship = ownedEntityTypeBuilder.HasRelationship( targetEntityType: principalBuilder.Metadata, navigationToTarget: inverse, inverseNavigation: navigation, @@ -3382,10 +3228,10 @@ private InternalForeignKeyBuilder HasOwnership( if (relationship?.Metadata.Builder == null) { - if (ownedEntityType.Metadata.Builder != null - && ownedEntityType.Metadata.HasDefiningNavigation()) + if (ownedEntityTypeBuilder.Metadata.Builder != null + && ownedEntityTypeBuilder.Metadata.HasSharedClrType) { - ModelBuilder.HasNoEntityType(ownedEntityType.Metadata, configurationSource); + ModelBuilder.HasNoEntityType(ownedEntityTypeBuilder.Metadata, configurationSource); } return null; @@ -3449,87 +3295,190 @@ public virtual InternalEntityTypeBuilder GetTargetEntityTypeBuilder( [NotNull] Type targetClrType, [NotNull] MemberInfo navigationInfo, ConfigurationSource? configurationSource) - { - var ownership = Metadata.FindOwnership(); + => GetTargetEntityTypeBuilder( + new TypeIdentity(targetClrType, Metadata.Model), MemberIdentity.Create(navigationInfo), configurationSource); - // ReSharper disable CheckForReferenceEqualityInstead.1 - // ReSharper disable CheckForReferenceEqualityInstead.3 - if (ownership != 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 InternalEntityTypeBuilder GetTargetEntityTypeBuilder( + TypeIdentity targetEntityType, + MemberIdentity navigation, + ConfigurationSource? configurationSource, + bool? targetShouldBeOwned = null) + { + var existingNavigation = Metadata.FindNavigation(navigation.Name); + if (existingNavigation != null) { - if (targetClrType.Equals(Metadata.ClrType)) + var existingTargetType = existingNavigation.TargetEntityType; + if ((!targetEntityType.IsNamed + || existingTargetType.Name == targetEntityType.Name) + && (targetEntityType.Type == null + || existingTargetType.ClrType == targetEntityType.Type)) + { + Check.DebugAssert(existingNavigation.ForeignKey.IsOwnership + || !existingNavigation.TargetEntityType.IsOwned(), + $"Found '{existingNavigation.DeclaringEntityType.ShortName()}.{existingNavigation.Name}'. " + + "Owned types should only have ownership navigations point at it"); + + return existingTargetType.HasSharedClrType + ? !existingTargetType.HasClrType + ? ModelBuilder.Entity(existingTargetType.Name, configurationSource.Value, targetShouldBeOwned) + : ModelBuilder.SharedTypeEntity( + 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 (targetClrType.IsAssignableFrom(ownership.PrincipalEntityType.ClrType)) + if (navigation.MemberInfo == null + && Metadata.HasClrType) + { + if (Metadata.GetRuntimeProperties().TryGetValue(navigation.Name, out var propertyInfo)) + { + navigation = new MemberIdentity(propertyInfo); + } + else if (Metadata.GetRuntimeFields().TryGetValue(navigation.Name, out var fieldInfo)) { - if (configurationSource != null) + navigation = new MemberIdentity(fieldInfo); + } + } + + var targetType = targetEntityType.Type; + if (targetType == null) + { + var memberType = navigation.MemberInfo?.GetMemberType(); + if (memberType != null) + { + targetType = memberType.TryGetSequenceType() ?? memberType; + + if (targetType != null + && targetEntityType.Name == Metadata.Model.GetDisplayName(targetType)) { - ownership.PrincipalEntityType.UpdateConfigurationSource(configurationSource.Value); + targetEntityType = new TypeIdentity(targetType, Metadata.Model); } - - return ownership.PrincipalEntityType.Builder; } } - var entityType = Metadata; - InternalEntityTypeBuilder targetEntityTypeBuilder = null; - if (!ModelBuilder.Metadata.EntityTypeShouldHaveDefiningNavigation(targetClrType)) + if (targetType == null) { - var targetEntityType = ModelBuilder.Metadata.FindEntityType(targetClrType); + targetType = Model.DefaultPropertyBagType; + } - var existingOwnership = targetEntityType?.FindOwnership(); - if (existingOwnership != null - && entityType.Model.IsOwned(targetClrType) - && (existingOwnership.PrincipalEntityType != entityType - || existingOwnership.PrincipalToDependent?.Name != navigationInfo.GetSimpleMemberName())) + if (targetShouldBeOwned != true) + { + var ownership = Metadata.FindOwnership(); + if (ownership != null) { - return configurationSource.HasValue - && !targetClrType.Equals(Metadata.ClrType) - ? ModelBuilder.Entity( - targetClrType, navigationInfo.GetSimpleMemberName(), entityType, configurationSource.Value) - : null; - } + if (targetType.Equals(Metadata.ClrType)) + { + // Avoid infinite recursion on self reference + return null; + } + + if (targetType.IsAssignableFrom(ownership.PrincipalEntityType.ClrType)) + { + if (configurationSource.HasValue) + { + ownership.PrincipalEntityType.UpdateConfigurationSource(configurationSource.Value); + } - var owned = existingOwnership != null - || entityType.Model.IsOwned(targetClrType); - targetEntityTypeBuilder = configurationSource.HasValue - ? ModelBuilder.Entity(targetClrType, configurationSource.Value, owned) - : targetEntityType?.Builder; + return ownership.PrincipalEntityType.Builder; + } + } } - else if (!targetClrType.Equals(Metadata.ClrType)) + + var targetTypeName = targetEntityType.IsNamed && (targetEntityType.Type != null || targetShouldBeOwned != true) + ? targetEntityType.Name + : Metadata.Model.IsShared(targetType) + ? Metadata.GetOwnedName(targetEntityType.IsNamed ? targetEntityType.Name : targetType.ShortDisplayName(), navigation.Name) + : Metadata.Model.GetDisplayName(targetType); + + var shouldBeOwned = targetShouldBeOwned ?? Metadata.Model.IsOwned(targetType); + var targetEntityTypeBuilder = ModelBuilder.Metadata.FindEntityType(targetTypeName)?.Builder; + if (targetEntityTypeBuilder != null + && shouldBeOwned) { - if (entityType.DefiningEntityType?.ClrType.Equals(targetClrType) == true) + var existingOwnership = targetEntityTypeBuilder.Metadata.FindDeclaredOwnership(); + if (existingOwnership != null) { - if (configurationSource != null) + if (!configurationSource.Overrides(ConfigurationSource.Explicit) + && navigation.MemberInfo != null + && Metadata.IsInOwnershipPath(targetType)) + { + return null; + } + + if (targetEntityType.IsNamed + && targetEntityType.Type != null) + { + if (configurationSource == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.ClashingNamedOwnedType( + targetTypeName, Metadata.DisplayName(), navigation.Name)); + } + + return null; + } + + if (existingOwnership.Builder.MakeDeclaringTypeShared(configurationSource) == null) { - entityType.DefiningEntityType.UpdateConfigurationSource(configurationSource.Value); + return null; } - return entityType.DefiningEntityType.Builder; + targetEntityTypeBuilder = null; + if (!targetEntityType.IsNamed) + { + targetTypeName = Metadata.GetOwnedName(targetType.ShortDisplayName(), navigation.Name); + } } + } - targetEntityTypeBuilder = - entityType.FindNavigation(navigationInfo.GetSimpleMemberName())?.TargetEntityType.Builder - ?? entityType.Model.FindEntityType( - targetClrType, navigationInfo.GetSimpleMemberName(), entityType)?.Builder; + if (targetEntityTypeBuilder == null) + { + if (configurationSource == null) + { + return null; + } - if (targetEntityTypeBuilder == null - && configurationSource.HasValue - && !entityType.IsInDefinitionPath(targetClrType) - && !entityType.IsInOwnershipPath(targetClrType)) + if (Metadata.Model.IsShared(targetType) + || targetEntityType.IsNamed) { - return ModelBuilder.Entity( - targetClrType, navigationInfo.GetSimpleMemberName(), entityType, configurationSource.Value); + if (shouldBeOwned != true + || (!configurationSource.Overrides(ConfigurationSource.Explicit) + && navigation.MemberInfo != null + && Metadata.IsInOwnershipPath(targetType))) + { + return null; + } + + targetEntityTypeBuilder = ModelBuilder.SharedTypeEntity( + targetTypeName, targetType, configurationSource.Value, shouldBeOwned); + } + else + { + targetEntityTypeBuilder = targetEntityType.IsNamed + ? targetType == null + ? ModelBuilder.Entity(targetTypeName, configurationSource.Value, shouldBeOwned) + : ModelBuilder.SharedTypeEntity(targetTypeName, targetType, configurationSource.Value, shouldBeOwned) + : ModelBuilder.Entity(targetType, configurationSource.Value, shouldBeOwned); } - if (configurationSource != null) + if (targetEntityTypeBuilder == null) { - targetEntityTypeBuilder?.Metadata.UpdateConfigurationSource(configurationSource.Value); + return null; } } - // ReSharper restore CheckForReferenceEqualityInstead.1 - // ReSharper restore CheckForReferenceEqualityInstead.3 return targetEntityTypeBuilder; } diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index 530f26aebdd..371f217bce9 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -202,7 +202,8 @@ private InternalForeignKeyBuilder HasNavigations( if (navigationToPrincipalName != null && navigationToPrincipal.Value.MemberInfo == null - && dependentEntityType.HasClrType) + && dependentEntityType.HasClrType + && dependentEntityType.ClrType != Model.DefaultPropertyBagType) { var navigationProperty = FindCompatibleClrMember( navigationToPrincipalName, dependentEntityType, principalEntityType, shouldThrow); @@ -214,7 +215,8 @@ private InternalForeignKeyBuilder HasNavigations( if (navigationToDependentName != null && navigationToDependent.Value.MemberInfo == null - && principalEntityType.HasClrType) + && principalEntityType.HasClrType + && principalEntityType.ClrType != Model.DefaultPropertyBagType) { var navigationProperty = FindCompatibleClrMember( navigationToDependentName, principalEntityType, dependentEntityType, shouldThrow); @@ -390,16 +392,8 @@ private InternalForeignKeyBuilder HasNavigations( { using var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions(); builder = this; - Metadata.UpdateConfigurationSource(configurationSource); - if (shouldBeUnique.HasValue) - { - IsUnique(shouldBeUnique.Value, configurationSource); - } - else - { - IsUnique(null, ConfigurationSource.Convention); - } + IsUnique(shouldBeUnique, shouldBeUnique.HasValue ? configurationSource : ConfigurationSource.Convention); if (navigationToPrincipal != null) { @@ -414,6 +408,7 @@ private InternalForeignKeyBuilder HasNavigations( Metadata.DeclaringEntityType.RemoveIgnored(navigationToPrincipalName); if (Metadata.DeclaringEntityType.ClrType != null + && Metadata.DeclaringEntityType.ClrType != Model.DefaultPropertyBagType && navigationProperty == null) { throw new InvalidOperationException( @@ -438,7 +433,8 @@ private InternalForeignKeyBuilder HasNavigations( { Metadata.PrincipalEntityType.RemoveIgnored(navigationToDependentName); - if (Metadata.DeclaringEntityType.ClrType != null + if (Metadata.PrincipalEntityType.ClrType != null + && Metadata.PrincipalEntityType.ClrType != Model.DefaultPropertyBagType && navigationProperty == null) { throw new InvalidOperationException( @@ -1045,13 +1041,8 @@ public virtual InternalForeignKeyBuilder IsOwnership(bool? ownership, Configurat return null; } - if (declaringType.HasDefiningNavigation()) + if (declaringType.HasSharedClrType) { - Check.DebugAssert( - Metadata.PrincipalToDependent == null - || declaringType.DefiningNavigationName == Metadata.PrincipalToDependent.Name, - "Unexpected navigation"); - if (otherOwnership != null && !configurationSource.Overrides(otherOwnership.GetConfigurationSource())) { @@ -1085,7 +1076,7 @@ public virtual InternalForeignKeyBuilder IsOwnership(bool? ownership, Configurat if (otherOwnership != null) { if (!Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit) - && Metadata.PrincipalEntityType.IsInDefinitionPath(Metadata.DeclaringEntityType.ClrType)) + && Metadata.PrincipalEntityType.IsInOwnershipPath(Metadata.DeclaringEntityType.ClrType)) { return null; } @@ -1108,22 +1099,17 @@ public virtual InternalForeignKeyBuilder IsOwnership(bool? ownership, Configurat var fk = newRelationshipBuilder.Metadata; fk.DeclaringEntityType.Builder.HasNoRelationship(fk, fk.GetConfigurationSource()); - if (otherOwnership.Builder.AddToDeclaringTypeDefinition(configurationSource) == null) + if (otherOwnership.Builder.MakeDeclaringTypeShared(configurationSource) == null) { return null; } - var newEntityType = declaringType.ClrType == null - ? ModelBuilder.Entity( - declaringType.Name, - Metadata.PrincipalToDependent.Name, - Metadata.PrincipalEntityType, - declaringType.GetConfigurationSource()).Metadata - : ModelBuilder.Entity( - declaringType.ClrType, - Metadata.PrincipalToDependent.Name, - Metadata.PrincipalEntityType, - declaringType.GetConfigurationSource()).Metadata; + var name = Metadata.PrincipalEntityType.GetOwnedName(declaringType.ShortName(), Metadata.PrincipalToDependent.Name); + var newEntityType = ModelBuilder.SharedTypeEntity( + name, + declaringType.ClrType, + declaringType.GetConfigurationSource(), + shouldBeOwned: true).Metadata; newRelationshipBuilder = newRelationshipBuilder.Attach(newEntityType.Builder); @@ -1176,31 +1162,28 @@ public virtual bool CanSetIsOwnership(bool? ownership, ConfigurationSource? conf /// 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 InternalForeignKeyBuilder AddToDeclaringTypeDefinition(ConfigurationSource configurationSource) + public virtual InternalForeignKeyBuilder MakeDeclaringTypeShared(ConfigurationSource? configurationSource) { - if (Metadata.DeclaringEntityType.HasDefiningNavigation()) + if (Metadata.DeclaringEntityType.HasSharedClrType) { return this; } - EntityType newEntityType; - if (Metadata.DeclaringEntityType.ClrType == null) + if (configurationSource == null) { - newEntityType = ModelBuilder.Entity( - Metadata.DeclaringEntityType.Name, - Metadata.PrincipalToDependent.Name, - Metadata.PrincipalEntityType, - Metadata.DeclaringEntityType.GetConfigurationSource()).Metadata; - } - else - { - newEntityType = ModelBuilder.Entity( - Metadata.DeclaringEntityType.ClrType, - Metadata.PrincipalToDependent.Name, - Metadata.PrincipalEntityType, - Metadata.DeclaringEntityType.GetConfigurationSource()).Metadata; + return null; } + Check.DebugAssert(Metadata.IsOwnership, "Expected an ownership"); + Check.DebugAssert(Metadata.PrincipalToDependent != null, "Expected a navigation to the dependent"); + + var name = Metadata.PrincipalEntityType.GetOwnedName(Metadata.DeclaringEntityType.ShortName(), Metadata.PrincipalToDependent.Name); + var newEntityType = ModelBuilder.SharedTypeEntity( + name, + Metadata.DeclaringEntityType.ClrType, + configurationSource.Value, + shouldBeOwned: true).Metadata; + var newOwnership = newEntityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership); if (newOwnership == null) { @@ -1510,6 +1493,9 @@ private InternalForeignKeyBuilder HasEntityTypes( Metadata.UpdatePrincipalEndConfigurationSource(principalEndConfigurationSource.Value); + principalEntityType.UpdateConfigurationSource(configurationSource); + dependentEntityType.UpdateConfigurationSource(configurationSource); + return (InternalForeignKeyBuilder)ModelBuilder.Metadata.ConventionDispatcher.OnForeignKeyPrincipalEndChanged(this); } @@ -1726,10 +1712,22 @@ public virtual InternalForeignKeyBuilder HasForeignKey( [CanBeNull] IReadOnlyList properties, [NotNull] EntityType dependentEntityType, ConfigurationSource configurationSource) - => HasForeignKey( - dependentEntityType.Builder.GetOrCreateProperties(properties, configurationSource), - dependentEntityType, - configurationSource); + { + using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) + { + var relationship = HasForeignKey( + dependentEntityType.Builder.GetOrCreateProperties(properties, configurationSource), + dependentEntityType, + configurationSource); + + if (relationship == null) + { + return null; + } + + return (InternalForeignKeyBuilder)batch.Run(relationship.Metadata)?.Builder; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1741,16 +1739,28 @@ public virtual InternalForeignKeyBuilder HasForeignKey( [CanBeNull] IReadOnlyList propertyNames, [NotNull] EntityType dependentEntityType, ConfigurationSource configurationSource) - => HasForeignKey( - dependentEntityType.Builder.GetOrCreateProperties( - propertyNames, - configurationSource, - Metadata.PrincipalKey.Properties, - Metadata.GetIsRequiredConfigurationSource() != null && Metadata.IsRequired, - Metadata.GetPrincipalKeyConfigurationSource() == null - && Metadata.PrincipalEntityType.FindPrimaryKey() == null), - dependentEntityType, - configurationSource); + { + using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) + { + var relationship = HasForeignKey( + dependentEntityType.Builder.GetOrCreateProperties( + propertyNames, + configurationSource, + Metadata.PrincipalKey.Properties, + Metadata.GetIsRequiredConfigurationSource() != null && Metadata.IsRequired, + Metadata.GetPrincipalKeyConfigurationSource() == null + && Metadata.PrincipalEntityType.FindPrimaryKey() == null), + dependentEntityType, + configurationSource); + + if (relationship == null) + { + return null; + } + + return (InternalForeignKeyBuilder)batch.Run(relationship.Metadata)?.Builder; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1786,6 +1796,7 @@ public virtual InternalForeignKeyBuilder HasForeignKey( properties = dependentEntityType.Builder.GetActualProperties(properties, configurationSource); if (Metadata.Properties.SequenceEqual(properties)) { + Metadata.UpdateConfigurationSource(configurationSource); Metadata.UpdatePropertiesConfigurationSource(configurationSource); var builder = this; @@ -1949,9 +1960,21 @@ private bool CanSetForeignKey( public virtual InternalForeignKeyBuilder HasPrincipalKey( [NotNull] IReadOnlyList members, ConfigurationSource configurationSource) - => HasPrincipalKey( - Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(members, configurationSource), - configurationSource); + { + using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) + { + var relationship = HasPrincipalKey( + Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(members, configurationSource), + configurationSource); + + if (relationship == null) + { + return null; + } + + return (InternalForeignKeyBuilder)batch.Run(relationship.Metadata)?.Builder; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1962,9 +1985,21 @@ public virtual InternalForeignKeyBuilder HasPrincipalKey( public virtual InternalForeignKeyBuilder HasPrincipalKey( [NotNull] IReadOnlyList propertyNames, ConfigurationSource configurationSource) - => HasPrincipalKey( - Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(propertyNames, configurationSource), - configurationSource); + { + using (var batch = Metadata.DeclaringEntityType.Model.ConventionDispatcher.DelayConventions()) + { + var relationship = HasPrincipalKey( + Metadata.PrincipalEntityType.Builder.GetOrCreateProperties(propertyNames, configurationSource), + configurationSource); + + if (relationship == null) + { + return null; + } + + return (InternalForeignKeyBuilder)batch.Run(relationship.Metadata)?.Builder; + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1989,6 +2024,7 @@ public virtual InternalForeignKeyBuilder HasPrincipalKey( if (Metadata.PrincipalKey.Properties.SequenceEqual(properties)) { + Metadata.UpdateConfigurationSource(configurationSource); Metadata.UpdatePrincipalKeyConfigurationSource(configurationSource); var builder = this; @@ -2266,13 +2302,15 @@ private InternalForeignKeyBuilder ReplaceForeignKey( Check.DebugAssert( navigationToPrincipal?.Name == null || navigationToPrincipal.Value.MemberInfo != null - || !dependentEntityTypeBuilder.Metadata.HasClrType, + || !dependentEntityTypeBuilder.Metadata.HasClrType + || dependentEntityTypeBuilder.Metadata.ClrType == Model.DefaultPropertyBagType, "Principal navigation check failed"); Check.DebugAssert( navigationToDependent?.Name == null || navigationToDependent.Value.MemberInfo != null - || !principalEntityTypeBuilder.Metadata.HasClrType, + || !principalEntityTypeBuilder.Metadata.HasClrType + || principalEntityTypeBuilder.Metadata.ClrType == Model.DefaultPropertyBagType, "Dependent navigation check failed"); Check.DebugAssert( @@ -3510,7 +3548,9 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil } else { - if (Metadata.PrincipalEntityType.Name == entityTypeBuilder.Metadata.Name) + if (Metadata.PrincipalEntityType.Name == entityTypeBuilder.Metadata.Name + || (Metadata.PrincipalEntityType.HasClrType + && Metadata.PrincipalEntityType.ClrType == entityTypeBuilder.Metadata.ClrType)) { principalEntityTypeBuilder = entityTypeBuilder; principalEntityType = entityTypeBuilder.Metadata; @@ -3520,13 +3560,15 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil principalEntityType = model.FindEntityType(Metadata.PrincipalEntityType.Name); if (principalEntityType == null) { - if (model.EntityTypeShouldHaveDefiningNavigation(Metadata.PrincipalEntityType.Name) - && Metadata.PrincipalEntityType.HasDefiningNavigation()) + var ownership = Metadata.PrincipalEntityType.FindOwnership(); + if (Metadata.PrincipalEntityType.HasSharedClrType + && ownership != null + && ownership.PrincipalEntityType.Builder != null) { principalEntityType = model.FindEntityType( - Metadata.PrincipalEntityType.Name, - Metadata.PrincipalEntityType.DefiningNavigationName, - Metadata.PrincipalEntityType.DefiningEntityType); + Metadata.PrincipalEntityType.ClrType, + ownership.PrincipalToDependent.Name, + ownership.PrincipalEntityType); if (principalEntityType == null) { return null; @@ -3543,11 +3585,11 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil } if (!Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit) - && principalEntityType.FindOwnership() != null + && principalEntityType.IsOwned() && Metadata.DependentToPrincipal != null && !Metadata.IsOwnership) { - // Only the owner can have a navigation to an owned type + // An entity type can have a navigation to a principal owned type only if it's the owner return null; } @@ -3560,9 +3602,11 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil } else { - if (Metadata.DeclaringEntityType.Name == entityTypeBuilder.Metadata.Name - && (principalEntityType != entityTypeBuilder.Metadata - || !principalEntityType.HasDefiningNavigation())) + if ((Metadata.DeclaringEntityType.Name == entityTypeBuilder.Metadata.Name + || (Metadata.DeclaringEntityType.HasClrType + && Metadata.DeclaringEntityType.ClrType == entityTypeBuilder.Metadata.ClrType)) + && (!principalEntityType.HasSharedClrType + || principalEntityType != entityTypeBuilder.Metadata)) { dependentEntityTypeBuilder = entityTypeBuilder; dependentEntityType = entityTypeBuilder.Metadata; @@ -3574,53 +3618,31 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil { using (ModelBuilder.Metadata.ConventionDispatcher.DelayConventions()) { - if (model.EntityTypeShouldHaveDefiningNavigation(Metadata.DeclaringEntityType.Name)) + if (Metadata.DeclaringEntityType.HasSharedClrType + || (Metadata.DeclaringEntityType.HasClrType + && model.IsShared(Metadata.DeclaringEntityType.ClrType))) { - if (Metadata.DeclaringEntityType.HasDefiningNavigation()) + if (Metadata.IsOwnership + && Metadata.PrincipalToDependent != null) { - dependentEntityType = model.FindEntityType( - Metadata.DeclaringEntityType.Name, - Metadata.DeclaringEntityType.DefiningNavigationName, - Metadata.DeclaringEntityType.DefiningEntityType); + var name = principalEntityType.GetOwnedName( + Metadata.DeclaringEntityType.ShortName(), Metadata.PrincipalToDependent.Name); + dependentEntityType = ModelBuilder.SharedTypeEntity( + name, + Metadata.DeclaringEntityType.ClrType, + Metadata.DeclaringEntityType.GetConfigurationSource(), + shouldBeOwned: null).Metadata; } - - if (dependentEntityType == null) + else { - if (Metadata.IsOwnership - && Metadata.PrincipalToDependent != null) - { - if (model.HasOtherEntityTypesWithDefiningNavigation(Metadata.DeclaringEntityType)) - { - dependentEntityType = Metadata.DeclaringEntityType.ClrType == null - ? model.AddEntityType( - Metadata.DeclaringEntityType.Name, - Metadata.PrincipalToDependent.Name, - principalEntityType, - configurationSource) - : model.AddEntityType( - Metadata.DeclaringEntityType.ClrType, - Metadata.PrincipalToDependent.Name, - principalEntityType, - configurationSource); - } - else - { - dependentEntityType = Metadata.DeclaringEntityType.ClrType == null - ? model.Builder.Entity(Metadata.DeclaringEntityType.Name, configurationSource).Metadata - : model.Builder.Entity(Metadata.DeclaringEntityType.ClrType, configurationSource).Metadata; - } - } - else - { - return null; - } + return null; } } else { dependentEntityType = Metadata.DeclaringEntityType.ClrType == null - ? model.AddEntityType(Metadata.DeclaringEntityType.Name, configurationSource) - : model.AddEntityType(Metadata.DeclaringEntityType.ClrType, configurationSource); + ? ModelBuilder.Entity(Metadata.DeclaringEntityType.Name, configurationSource).Metadata + : ModelBuilder.Entity(Metadata.DeclaringEntityType.ClrType, configurationSource).Metadata; } } } @@ -3630,12 +3652,10 @@ public virtual InternalForeignKeyBuilder Attach([NotNull] InternalEntityTypeBuil } if (!Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit) - && ((dependentEntityType.HasDefiningNavigation() - && (Metadata.PrincipalToDependent?.Name != dependentEntityType.DefiningNavigationName - || principalEntityType != dependentEntityType.DefiningEntityType)) - || (dependentEntityType.FindOwnership() != null - && Metadata.PrincipalToDependent != null))) + && dependentEntityType.IsOwned() + && Metadata.PrincipalToDependent != null) { + // Only the owner can have a navigation to an owned type return null; } diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 451a85c9c2f..02628048ea6 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata.Internal @@ -61,10 +60,10 @@ public virtual InternalEntityTypeBuilder Entity( /// public virtual InternalEntityTypeBuilder SharedTypeEntity( [NotNull] string name, - [NotNull] Type type, + [CanBeNull] Type type, ConfigurationSource configurationSource, bool? shouldBeOwned = false) - => Entity(new TypeIdentity(name, Check.NotNull(type, nameof(type))), configurationSource, shouldBeOwned); + => Entity(new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), configurationSource, shouldBeOwned); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -91,26 +90,32 @@ private InternalEntityTypeBuilder Entity( using var batch = Metadata.ConventionDispatcher.DelayConventions(); var clrType = type.Type; EntityType entityType; + EntityType.Snapshot entityTypeSnapshot = null; if (type.IsNamed) { - if (type.Type != null) + if (clrType != null) { - var nonSharedTypes = Metadata.GetEntityTypes(Metadata.GetDisplayName(type.Type)); - foreach (var nonSharedType in nonSharedTypes) + entityType = Metadata.FindEntityType(clrType); + if (entityType != null) { - if (configurationSource.OverridesStrictly(nonSharedType.GetConfigurationSource())) + if (entityType.Name == type.Name + && entityType.HasSharedClrType) { - continue; + entityType.UpdateConfigurationSource(configurationSource); + return entityType.Builder; } - return configurationSource == ConfigurationSource.Explicit - ? throw new InvalidOperationException(CoreStrings.ClashingNonSharedType(type.Name, type.Type.DisplayName())) - : (InternalEntityTypeBuilder)null; - } + if (!configurationSource.OverridesStrictly(entityType.GetConfigurationSource()) + && !entityType.IsOwned()) + { + return configurationSource == ConfigurationSource.Explicit + ? throw new InvalidOperationException(CoreStrings.ClashingNonSharedType(type.Name, clrType.ShortDisplayName())) + : (InternalEntityTypeBuilder)null; + } - foreach (var nonSharedType in nonSharedTypes) - { - HasNoEntityType(nonSharedType, configurationSource); + entityTypeSnapshot = InternalEntityTypeBuilder.DetachAllMembers(entityType); + + HasNoEntityType(entityType, ConfigurationSource.Explicit); } } @@ -118,10 +123,11 @@ private InternalEntityTypeBuilder Entity( } else { + clrType = type.Type ?? Metadata.FindClrType(type.Name); if (Metadata.IsShared(clrType)) { return configurationSource == ConfigurationSource.Explicit - ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.DisplayName())) + ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.ShortDisplayName())) : (InternalEntityTypeBuilder)null; } @@ -129,10 +135,10 @@ private InternalEntityTypeBuilder Entity( } if (shouldBeOwned == false - && (ShouldBeOwnedType(type) // Marked in model as owned - || entityType != null && entityType.IsOwned())) // Created using Owns* API + && (ShouldBeOwnedType(type) + || entityType != null && entityType.IsOwned())) { - // We always throw as configuring a type as owned is always comes from user (through Explicit/DataAnnotation) + // We always throw as configuring a type as owned always comes from user (through Explicit/DataAnnotation) throw new InvalidOperationException( CoreStrings.ClashingOwnedEntityType( clrType == null ? type.Name : clrType.ShortDisplayName())); @@ -145,7 +151,7 @@ private InternalEntityTypeBuilder Entity( && configurationSource == ConfigurationSource.Explicit && entityType.GetConfigurationSource() == ConfigurationSource.Explicit) { - throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(entityType.DisplayName())); + throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(clrType.ShortDisplayName())); } foreach (var derivedType in entityType.GetDerivedTypes()) @@ -184,11 +190,17 @@ private InternalEntityTypeBuilder Entity( Metadata.RemoveIgnored(type.Name); entityType = type.IsNamed - ? type.Type == null + ? clrType == null ? Metadata.AddEntityType(type.Name, configurationSource) - : Metadata.AddEntityType(type.Name, type.Type, configurationSource) + : Metadata.AddEntityType(type.Name, clrType, configurationSource) : Metadata.AddEntityType(clrType, configurationSource); + if (entityType != null + && entityTypeSnapshot != null) + { + entityTypeSnapshot.Attach(entityType.Builder); + } + return entityType?.Builder; } @@ -223,67 +235,9 @@ private InternalEntityTypeBuilder Entity( string definingNavigationName, EntityType definingEntityType, ConfigurationSource configurationSource) - { - if (IsIgnored(type, configurationSource)) - { - return null; - } - - var clrType = type.Type - ?? Metadata.FindClrType(type.Name); - - var entityTypeWithDefiningNavigation = clrType == null - ? Metadata.FindEntityType(type.Name, definingNavigationName, definingEntityType) - : Metadata.FindEntityType(clrType, definingNavigationName, definingEntityType); - if (entityTypeWithDefiningNavigation == null) - { - var entityType = clrType == null - ? Metadata.FindEntityType(type.Name) - : Metadata.FindEntityType(clrType); - - IConventionBatch batch = null; - EntityType.Snapshot entityTypeSnapshot = null; - if (entityType != null) - { - if (!configurationSource.Overrides(entityType.GetConfigurationSource())) - { - return null; - } - - batch = ModelBuilder.Metadata.ConventionDispatcher.DelayConventions(); - entityTypeSnapshot = InternalEntityTypeBuilder.DetachAllMembers(entityType); - - HasNoEntityType(entityType, configurationSource); - } - - if (clrType == null) - { - Metadata.RemoveIgnored(type.Name); - - entityTypeWithDefiningNavigation = Metadata.AddEntityType( - type.Name, definingNavigationName, definingEntityType, configurationSource); - } - else - { - Metadata.RemoveIgnored(type.Name); - - entityTypeWithDefiningNavigation = Metadata.AddEntityType( - clrType, definingNavigationName, definingEntityType, configurationSource); - } - - if (batch != null) - { - entityTypeSnapshot.Attach(entityTypeWithDefiningNavigation.Builder); - batch.Dispose(); - } - } - else - { - entityTypeWithDefiningNavigation.UpdateConfigurationSource(configurationSource); - } - - return entityTypeWithDefiningNavigation?.Builder; - } + => SharedTypeEntity( + definingEntityType.GetOwnedName(type.Type?.ShortDisplayName() ?? type.Name, definingNavigationName), + type.Type, configurationSource, shouldBeOwned: true); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -326,9 +280,13 @@ public virtual IConventionOwnedEntityTypeBuilder Owned( Metadata.RemoveIgnored(type); Metadata.AddOwned(type); - var entityType = Metadata.FindEntityType(type); - if (entityType?.GetForeignKeys().Any(fk => fk.IsOwnership) == false) + foreach (var entityType in Metadata.GetEntityTypes(type)) { + if (entityType.IsOwned()) + { + continue; + } + if (!configurationSource.Overrides(entityType.GetConfigurationSource())) { return null; @@ -336,20 +294,42 @@ public virtual IConventionOwnedEntityTypeBuilder Owned( if (entityType.GetConfigurationSource() == ConfigurationSource.Explicit) { - throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(entityType.DisplayName())); + throw new InvalidOperationException(CoreStrings.ClashingNonOwnedEntityType(type.ShortDisplayName())); } - var ownershipCandidate = entityType.GetForeignKeys().FirstOrDefault( + foreach (var derivedType in entityType.GetDerivedTypes()) + { + if (!derivedType.IsOwned() + && configurationSource == ConfigurationSource.Explicit + && derivedType.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.ClashingNonOwnedDerivedEntityType(type.ShortDisplayName(), derivedType.ShortName())); + } + } + + var ownershipCandidates = entityType.GetForeignKeys().Where( fk => fk.PrincipalToDependent != null - && !fk.PrincipalEntityType.IsInOwnershipPath(entityType) - && !fk.PrincipalEntityType.IsInDefinitionPath(type)); - if (ownershipCandidate != null) + && !fk.PrincipalEntityType.IsInOwnershipPath(type)).ToList(); + if (ownershipCandidates.Count == 1) { - if (ownershipCandidate.Builder.IsOwnership(true, configurationSource) == null) + if (ownershipCandidates[0].Builder.IsOwnership(true, configurationSource) == null) { return null; } } + else if (ownershipCandidates.Count > 1) + { + using (var batch = ModelBuilder.Metadata.ConventionDispatcher.DelayConventions()) + { + var ownership = ownershipCandidates[0].Builder.IsOwnership(true, configurationSource); + if (ownership == null) + { + return null; + } + ownership.MakeDeclaringTypeShared(configurationSource); + } + } else { if (!entityType.Builder.RemoveNonOwnershipRelationships(null, configurationSource)) @@ -435,39 +415,19 @@ private InternalModelBuilder Ignore(in TypeIdentity type, ConfigurationSource co using (Metadata.ConventionDispatcher.DelayConventions()) { - var removed = false; - foreach (var entityType in Metadata.GetEntityTypes(name).ToList()) + var entityType = Metadata.FindEntityType(name); + if (entityType != null) { - if (entityType.HasClrType) - { - if (entityType.HasSharedClrType) - { - Metadata.AddIgnored(entityType.Name, entityType.ClrType, configurationSource); - } - else - { - Metadata.AddIgnored(entityType.ClrType, configurationSource); - } - } - else - { - Metadata.AddIgnored(entityType.Name, configurationSource); - } - - removed = true; HasNoEntityType(entityType, configurationSource); } - if (!removed) + if (type.Type == null) { - if (type.Type == null) - { - Metadata.AddIgnored(name, configurationSource); - } - else - { - Metadata.AddIgnored(type.Type, configurationSource); - } + Metadata.AddIgnored(name, configurationSource); + } + else + { + Metadata.AddIgnored(type.Type, configurationSource); } if (type.Type != null) @@ -511,7 +471,13 @@ private bool CanIgnore(in TypeIdentity type, ConfigurationSource configurationSo return false; } - if (Metadata.GetEntityTypes(name).Any(o => !configurationSource.Overrides(o.GetConfigurationSource()))) + if (type.Type != null + && Metadata.GetEntityTypes(type.Type).Any(o => !configurationSource.Overrides(o.GetConfigurationSource()))) + { + return false; + } + + if (Metadata.FindEntityType(name)?.GetConfigurationSource().OverridesStrictly(configurationSource) == true) { return false; } @@ -562,11 +528,6 @@ public virtual InternalModelBuilder HasNoEntityType([NotNull] EntityType entityT Check.DebugAssert(derivedEntityTypeBuilder != null, "derivedEntityTypeBuilder is null"); } - foreach (var definedType in Metadata.GetEntityTypes().Where(e => e.DefiningEntityType == entityType).ToList()) - { - HasNoEntityType(definedType, configurationSource); - } - Metadata.RemoveEntityType(entityType); } diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 8998c5c990b..54069803b2f 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -51,17 +51,12 @@ private readonly SortedDictionary _entityTypes private readonly ConcurrentDictionary _clrTypeNameMap = new ConcurrentDictionary(); - private readonly SortedDictionary> _entityTypesWithDefiningNavigation - = new SortedDictionary>(StringComparer.Ordinal); - - private readonly SortedDictionary> _detachedEntityTypesWithDefiningNavigation - = new SortedDictionary>(StringComparer.Ordinal); - private readonly Dictionary _ignoredTypeNames = new Dictionary(StringComparer.Ordinal); - private readonly Dictionary _sharedTypes = - new Dictionary { { DefaultPropertyBagType, ConfigurationSource.Convention } }; + private readonly Dictionary Types)> _sharedTypes = + new Dictionary)> + { { DefaultPropertyBagType, (ConfigurationSource.Convention, new SortedSet(EntityTypeFullNameComparer.Instance)) } }; private bool? _skipDetectChanges; private ChangeTrackingStrategy? _changeTrackingStrategy; @@ -101,7 +96,6 @@ public Model([NotNull] ConventionSet conventions, [CanBeNull] ModelDependencies? /// 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. /// - // Becomes null once the model becomes read only; after this point, should never get accessed. public virtual ConventionDispatcher ConventionDispatcher { get; private set; } /// @@ -144,7 +138,7 @@ public virtual bool IsReadonly /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetEntityTypes() - => _entityTypes.Values.Concat(_entityTypesWithDefiningNavigation.Values.SelectMany(e => e)); + => _entityTypes.Values; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -194,6 +188,11 @@ public virtual IEnumerable GetEntityTypes() Check.NotEmpty(name, nameof(name)); Check.NotNull(type, nameof(type)); + if (GetDisplayName(type) == name) + { + throw new InvalidOperationException(CoreStrings.AmbiguousSharedTypeEntityTypeName(name)); + } + var entityType = new EntityType(name, type, this, configurationSource); return AddEntityType(entityType); @@ -202,75 +201,39 @@ public virtual IEnumerable GetEntityTypes() private EntityType? AddEntityType(EntityType entityType) { var entityTypeName = entityType.Name; - if (entityType.HasDefiningNavigation()) - { - if (_entityTypes.ContainsKey(entityTypeName)) - { - throw new InvalidOperationException(CoreStrings.ClashingNonWeakEntityType(entityType.DisplayName())); - } - - if (_detachedEntityTypesWithDefiningNavigation.TryGetValue(entityTypeName, out var detachedEntityTypes)) - { - for (var i = 0; i < detachedEntityTypes.Count; i++) - { - var (definingNavigation, definingEntityType) = detachedEntityTypes[i]; - if (definingNavigation == entityType.DefiningNavigationName - && definingEntityType == entityType.DefiningEntityType.Name) - { - detachedEntityTypes.RemoveAt(i); - break; - } - } - } - - if (!_entityTypesWithDefiningNavigation.TryGetValue(entityTypeName, out var entityTypesWithSameType)) - { - entityTypesWithSameType = new SortedSet(EntityTypeFullNameComparer.Instance); - _entityTypesWithDefiningNavigation[entityTypeName] = entityTypesWithSameType; - } - var added = entityTypesWithSameType.Add(entityType); - Check.DebugAssert(added, "added is false"); - } - else + if (_entityTypes.ContainsKey(entityTypeName)) { - if (_entityTypesWithDefiningNavigation.ContainsKey(entityTypeName)) - { - throw new InvalidOperationException(CoreStrings.ClashingWeakEntityType(entityType.DisplayName())); - } - - _detachedEntityTypesWithDefiningNavigation.Remove(entityTypeName); + throw new InvalidOperationException(CoreStrings.DuplicateEntityType(entityType.DisplayName())); + } - if (_entityTypes.ContainsKey(entityTypeName)) + if (entityType.HasSharedClrType) + { + if (_entityTypes.Any(et => !et.Value.HasSharedClrType && et.Value.ClrType == entityType.ClrType)) { - throw new InvalidOperationException(CoreStrings.DuplicateEntityType(entityType.DisplayName())); + throw new InvalidOperationException( + CoreStrings.ClashingNonSharedType(entityType.Name, entityType.ClrType.DisplayName())); } - if (entityType.HasSharedClrType) + if (_sharedTypes.TryGetValue(entityType.ClrType, out var existingTypes)) { - if (_entityTypes.Any(et => !et.Value.HasSharedClrType && et.Value.ClrType == entityType.ClrType)) - { - throw new InvalidOperationException( - CoreStrings.ClashingNonSharedType(entityType.Name, entityType.ClrType.DisplayName())); - } - - if (_sharedTypes.TryGetValue(entityType.ClrType, out var existingConfigurationSource)) - { - _sharedTypes[entityType.ClrType] = entityType.GetConfigurationSource().Max(existingConfigurationSource); - } - else - { - _sharedTypes.Add(entityType.ClrType, entityType.GetConfigurationSource()); - } + var newConfigurationSource = entityType.GetConfigurationSource().Max(existingTypes.ConfigurationSource); + existingTypes.Types.Add(entityType); + _sharedTypes[entityType.ClrType] = (newConfigurationSource, existingTypes.Types); } - else if (entityType.ClrType != null - && _sharedTypes.ContainsKey(entityType.ClrType)) + else { - throw new InvalidOperationException(CoreStrings.ClashingSharedType(entityType.DisplayName())); + var types = new SortedSet(EntityTypeFullNameComparer.Instance) { entityType }; + _sharedTypes.Add(entityType.ClrType, (entityType.GetConfigurationSource(), types)); } - - _entityTypes.Add(entityTypeName, entityType); } + else if (entityType.ClrType != null + && _sharedTypes.ContainsKey(entityType.ClrType)) + { + throw new InvalidOperationException(CoreStrings.ClashingSharedType(entityType.DisplayName())); + } + + _entityTypes.Add(entityTypeName, entityType); return (EntityType?)ConventionDispatcher.OnEntityTypeAdded(entityType.Builder)?.Metadata; } @@ -363,28 +326,15 @@ private static void AssertCanRemove(EntityType entityType) AssertCanRemove(entityType); - var entityTypeName = entityType.Name; - if (entityType.HasDefiningNavigation()) + if (entityType.ClrType != null + && _sharedTypes.TryGetValue(entityType.ClrType, out var existingTypes)) { - if (!_entityTypesWithDefiningNavigation.TryGetValue(entityTypeName, out var entityTypesWithSameType)) - { - return null; - } - - var removed = entityTypesWithSameType.Remove(entityType); - Check.DebugAssert(removed, "removed is false"); - - if (entityTypesWithSameType.Count == 0) - { - _entityTypesWithDefiningNavigation.Remove(entityTypeName); - } - } - else - { - var removed = _entityTypes.Remove(entityTypeName); - Check.DebugAssert(removed, "removed is false"); + existingTypes.Types.Remove(entityType); } + var removed = _entityTypes.Remove(entityType.Name); + Check.DebugAssert(removed, "removed is false"); + entityType.OnTypeRemoved(); return entityType; @@ -404,7 +354,8 @@ private static void AssertCanRemove(EntityType entityType) { Check.NotEmpty(name, nameof(name)); - var entityType = new EntityType(name, this, definingNavigationName, definingEntityType, configurationSource); + name = definingEntityType.GetOwnedName(name, definingNavigationName); + var entityType = new EntityType(name, DefaultPropertyBagType, this, configurationSource); return AddEntityType(entityType); } @@ -423,31 +374,12 @@ private static void AssertCanRemove(EntityType entityType) { Check.NotNull(type, nameof(type)); - var entityType = new EntityType(type, this, definingNavigationName, definingEntityType, configurationSource); + var name = definingEntityType.GetOwnedName(type.ShortDisplayName(), definingNavigationName); + var entityType = new EntityType(name, type, this, configurationSource); return AddEntityType(entityType); } - /// - /// 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 void AddDetachedEntityType( - [NotNull] string name, - [NotNull] string definingNavigationName, - [NotNull] string definingEntityTypeName) - { - if (!_detachedEntityTypesWithDefiningNavigation.TryGetValue(name, out var entityTypesWithSameType)) - { - entityTypesWithSameType = new List<(string, string)>(); - _detachedEntityTypesWithDefiningNavigation[name] = entityTypesWithSameType; - } - - entityTypesWithSameType.Add((definingNavigationName, definingEntityTypeName)); - } - /// /// 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 @@ -458,80 +390,6 @@ public virtual void AddDetachedEntityType( public virtual string GetDisplayName([NotNull] Type type) => _clrTypeNameMap.GetOrAdd(type, t => t.DisplayName()); - /// - /// 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 HasEntityTypeWithDefiningNavigation([NotNull] Type type) - => HasEntityTypeWithDefiningNavigation(GetDisplayName(type)); - - /// - /// 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 HasEntityTypeWithDefiningNavigation([NotNull] string name) - => _entityTypesWithDefiningNavigation.ContainsKey(name); - - /// - /// 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 HasOtherEntityTypesWithDefiningNavigation([NotNull] EntityType entityType) - { - if (!entityType.HasDefiningNavigation()) - { - return false; - } - - if (_entityTypesWithDefiningNavigation.TryGetValue(entityType.Name, out var entityTypesWithSameType)) - { - if (entityTypesWithSameType.Any(e => EntityTypeFullNameComparer.Instance.Compare(e, entityType) != 0)) - { - return true; - } - } - - if (_detachedEntityTypesWithDefiningNavigation.TryGetValue(entityType.Name, out var detachedEntityTypesWithSameType)) - { - if (detachedEntityTypesWithSameType.Any( - e => e.Item1 != entityType.DefiningNavigationName || e.Item2 != entityType.DefiningEntityType.Name)) - { - return true; - } - } - - return false; - } - - /// - /// 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 EntityTypeShouldHaveDefiningNavigation([NotNull] Type type) - => EntityTypeShouldHaveDefiningNavigation(new TypeIdentity(type, 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 EntityTypeShouldHaveDefiningNavigation([NotNull] string name) - => EntityTypeShouldHaveDefiningNavigation(new TypeIdentity(name)); - - private bool EntityTypeShouldHaveDefiningNavigation(in TypeIdentity type) - => _entityTypesWithDefiningNavigation.ContainsKey(type.Name) - || (_detachedEntityTypesWithDefiningNavigation.TryGetValue(type.Name, out var detachedTypes) - && detachedTypes.Count > 1); - /// /// 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 @@ -542,7 +400,7 @@ private bool EntityTypeShouldHaveDefiningNavigation(in TypeIdentity type) [NotNull] Type type, [NotNull] string definingNavigationName, [NotNull] EntityType definingEntityType) - => FindEntityType(GetDisplayName(type), definingNavigationName, definingEntityType); + => FindEntityType(type.ShortDisplayName(), definingNavigationName, definingEntityType); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -554,10 +412,7 @@ private bool EntityTypeShouldHaveDefiningNavigation(in TypeIdentity type) [NotNull] string name, [NotNull] string definingNavigationName, [NotNull] EntityType definingEntityType) - => !_entityTypesWithDefiningNavigation.TryGetValue(name, out var entityTypesWithSameType) - ? null - : entityTypesWithSameType - .FirstOrDefault(e => e.DefiningNavigationName == definingNavigationName && e.DefiningEntityType == definingEntityType); + => FindEntityType(definingEntityType.GetOwnedName(name, definingNavigationName)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -568,10 +423,13 @@ private bool EntityTypeShouldHaveDefiningNavigation(in TypeIdentity type) public virtual EntityType? FindActualEntityType([NotNull] EntityType entityType) => entityType.Builder != null ? entityType - : (entityType.HasDefiningNavigation() - ? FindActualEntityType(entityType.DefiningEntityType) - ?.FindNavigation(entityType.DefiningNavigationName)?.TargetEntityType - : FindEntityType(entityType.Name)); + : FindEntityType(entityType.Name) + ?? (entityType.HasSharedClrType + ? entityType.FindOwnership() is ForeignKey ownership + ? FindActualEntityType(ownership.PrincipalEntityType) + ?.FindNavigation(ownership.PrincipalToDependent!.Name)?.TargetEntityType + : null + : null); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -584,9 +442,7 @@ private bool EntityTypeShouldHaveDefiningNavigation(in TypeIdentity type) ? entityType.HasSharedClrType ? null : entityType.ClrType - : (_entityTypesWithDefiningNavigation.TryGetValue(name, out var entityTypesWithSameType) - ? entityTypesWithSameType.FirstOrDefault()?.ClrType - : null); + : null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -594,8 +450,13 @@ private bool EntityTypeShouldHaveDefiningNavigation(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 IReadOnlyCollection GetEntityTypes([NotNull] Type type) - => GetEntityTypes(GetDisplayName(type)); + public virtual IEnumerable GetEntityTypes([NotNull] Type type) + { + var result = GetEntityTypes(GetDisplayName(type)); + return _sharedTypes.TryGetValue(type, out var existingTypes) + ? result.Concat(existingTypes.Types) + : result; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -605,11 +466,6 @@ public virtual IReadOnlyCollection GetEntityTypes([NotNull] Type typ /// public virtual IReadOnlyCollection GetEntityTypes([NotNull] string name) { - if (_entityTypesWithDefiningNavigation.TryGetValue(name, out var entityTypesWithSameType)) - { - return entityTypesWithSameType; - } - var entityType = FindEntityType(name); return entityType == null ? Array.Empty() @@ -840,7 +696,7 @@ public virtual bool IsOwned([NotNull] Type type) /// public virtual ConfigurationSource? FindIsOwnedConfigurationSource([NotNull] Type type) { - if (!(this[CoreAnnotationNames.OwnedTypes] is Dictionary ownedTypes)) + if (this[CoreAnnotationNames.OwnedTypes] is not Dictionary ownedTypes) { return null; } @@ -914,13 +770,13 @@ public virtual void AddShared([NotNull] Type type, ConfigurationSource configura throw new InvalidOperationException(CoreStrings.CannotMarkShared(type.ShortDisplayName())); } - if (_sharedTypes.TryGetValue(type, out var existingConfigurationSource)) + if (_sharedTypes.TryGetValue(type, out var existingTypes)) { - _sharedTypes[type] = configurationSource.Max(existingConfigurationSource); + _sharedTypes[type] = (configurationSource.Max(existingTypes.ConfigurationSource), existingTypes.Types); } else { - _sharedTypes.Add(type, configurationSource); + _sharedTypes.Add(type, (configurationSource, new SortedSet(EntityTypeFullNameComparer.Instance))); } } @@ -1281,6 +1137,7 @@ IConventionAnnotatableBuilder IConventionAnnotatable.Builder /// 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. /// + [Obsolete] IConventionEntityType? IConventionModel.AddEntityType( string name, string definingNavigationName, @@ -1296,6 +1153,7 @@ IConventionAnnotatableBuilder IConventionAnnotatable.Builder /// 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. /// + [Obsolete] IConventionEntityType? IConventionModel.AddEntityType( Type type, string definingNavigationName, diff --git a/src/EFCore/Metadata/Internal/RelationshipSnapshot.cs b/src/EFCore/Metadata/Internal/RelationshipSnapshot.cs index 72ee9226e7b..e2941744e14 100644 --- a/src/EFCore/Metadata/Internal/RelationshipSnapshot.cs +++ b/src/EFCore/Metadata/Internal/RelationshipSnapshot.cs @@ -23,11 +23,11 @@ public class RelationshipSnapshot /// public RelationshipSnapshot( [NotNull] InternalForeignKeyBuilder relationship, - [CanBeNull] EntityType.Snapshot definedEntityTypeSnapshot, + [CanBeNull] EntityType.Snapshot ownedEntityTypeSnapshot, [CanBeNull] List<(SkipNavigation, ConfigurationSource)> referencingSkipNavigations) { Relationship = relationship; - DefinedEntityTypeSnapshot = definedEntityTypeSnapshot; + OwnedEntityTypeSnapshot = ownedEntityTypeSnapshot; ReferencingSkipNavigations = referencingSkipNavigations; } @@ -45,7 +45,7 @@ public RelationshipSnapshot( /// 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.Snapshot DefinedEntityTypeSnapshot { [DebuggerStepThrough] get; } + public virtual EntityType.Snapshot OwnedEntityTypeSnapshot { [DebuggerStepThrough] get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -71,7 +71,7 @@ public virtual InternalForeignKeyBuilder Attach([CanBeNull] InternalEntityTypeBu var newRelationship = Relationship.Attach(entityTypeBuilder); if (newRelationship != null) { - DefinedEntityTypeSnapshot?.Attach( + OwnedEntityTypeSnapshot?.Attach( newRelationship.Metadata.ResolveOtherEntityType(entityTypeBuilder.Metadata).Builder); if (ReferencingSkipNavigations != null) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 3f361f3c748..ce3b147bef7 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -43,7 +43,7 @@ public static string AddingProxyTypeAsEntityType([CanBeNull] object? typeName) typeName); /// - /// The entity type '{entityType}' has a defining navigation and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. + /// The entity type '{entityType}' uses a shared type and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. /// public static string AmbiguousDependentEntity([CanBeNull] object? entityType, [CanBeNull] object? targetEntryCall) => string.Format( @@ -106,6 +106,14 @@ public static string AmbiguousServiceProperty([CanBeNull] object? property, [Can GetString("AmbiguousServiceProperty", nameof(property), nameof(serviceType), nameof(entityType)), property, serviceType, entityType); + /// + /// The shared type entity type '{entityType}' cannot be added to the model because its name is the same as the CLR type name. This usually indicates an error, either add it as a non-shared entity type or choose a different name. + /// + public static string AmbiguousSharedTypeEntityTypeName([CanBeNull] object? entityType) + => string.Format( + GetString("AmbiguousSharedTypeEntityTypeName", nameof(entityType)), + entityType); + /// /// The annotation '{annotation}' was not found. Ensure that the annotation has been added to the object {annotatable} /// @@ -322,6 +330,7 @@ public static string ClashingNonSharedType([CanBeNull] object? entityType, [CanB /// /// The entity type '{entityType}' with a defining navigation cannot be added to the model because an entity type with the same name already exists. /// + [Obsolete] public static string ClashingNonWeakEntityType([CanBeNull] object? entityType) => string.Format( GetString("ClashingNonWeakEntityType", nameof(entityType)), @@ -346,6 +355,7 @@ public static string ClashingSharedType([CanBeNull] object? entityType) /// /// The entity type '{entityType}' cannot be added to the model because an entity type with a defining navigation with the same name already exists. /// + [Obsolete] public static string ClashingWeakEntityType([CanBeNull] object? entityType) => string.Format( GetString("ClashingWeakEntityType", nameof(entityType)), @@ -948,6 +958,7 @@ public static string ForeignKeyReferencedEntityKeyMismatch([CanBeNull] object? p /// /// The foreign keys on entity type '{dependentType}' cannot target the same entity type because it has a defining navigation. /// + [Obsolete] public static string ForeignKeySelfReferencingDependentEntityType([CanBeNull] object? dependentType) => string.Format( GetString("ForeignKeySelfReferencingDependentEntityType", nameof(dependentType)), @@ -1090,6 +1101,7 @@ public static string InconsistentInheritance([CanBeNull] object? entityType, [Ca /// /// The entity type '{ownedEntityType}' is configured as owned, but the entity type '{nonOwnedEntityType}' is not. Configure all entity types with defining navigations sharing a CLR type as owned in 'OnModelCreating'. /// + [Obsolete] public static string InconsistentOwnership([CanBeNull] object? ownedEntityType, [CanBeNull] object? nonOwnedEntityType) => string.Format( GetString("InconsistentOwnership", nameof(ownedEntityType), nameof(nonOwnedEntityType)), @@ -1283,6 +1295,7 @@ public static string InvalidSetTypeOwned([CanBeNull] object? typeName, [CanBeNul /// /// Cannot create a DbSet for '{typeName}' because it is mapped to multiple entity types with defining navigations and should be accessed through the owning entities. /// + [Obsolete] public static string InvalidSetTypeWeak([CanBeNull] object? typeName) => string.Format( GetString("InvalidSetTypeWeak", nameof(typeName)), @@ -1657,6 +1670,7 @@ public static string NoClrNavigation([CanBeNull] object? navigation, [CanBeNull] /// /// The navigation '{navigation}' used to define the entity type '{entityType}' is not present on '{definingEntityType}'. /// + [Obsolete] public static string NoDefiningNavigation([CanBeNull] object? navigation, [CanBeNull] object? entityType, [CanBeNull] object? definingEntityType) => string.Format( GetString("NoDefiningNavigation", nameof(navigation), nameof(entityType), nameof(definingEntityType)), @@ -1766,6 +1780,7 @@ public static string NonConfiguredNavigationToSharedType([CanBeNull] object? nav /// /// The entity type '{2_entityType}' owned by '{0_ownershipNavigation}' should use defining navigation '{1_definingNavigation}' for . /// + [Obsolete] public static string NonDefiningOwnership([CanBeNull] object? ownershipNavigation, [CanBeNull] object? definingNavigation, [CanBeNull] object? entityType) => string.Format( GetString("NonDefiningOwnership", "0_ownershipNavigation", "1_definingNavigation", "2_entityType"), @@ -2644,7 +2659,7 @@ public static string UnnamedIndexDefinedOnNonExistentProperty([CanBeNull] object entityType, indexProperties, propertyName); /// - /// The entity type '{entityType}' has a defining navigation and the supplied entity is currently not being tracked. To start tracking this entity, call '{referenceCall}' or '{collectionCall}' on the owner entry. + /// The entity type '{entityType}' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '{referenceCall}' or '{collectionCall}' on the owner entry. /// public static string UntrackedDependentEntity([CanBeNull] object? entityType, [CanBeNull] object? referenceCall, [CanBeNull] object? collectionCall) => string.Format( @@ -2686,6 +2701,7 @@ public static string WarningAsErrorTemplate([CanBeNull] object? eventName, [CanB /// /// The type '{entityType}' cannot have entity type '{baseType}' as the base type because the latter has a defining navigation. /// + [Obsolete] public static string WeakBaseType([CanBeNull] object? entityType, [CanBeNull] object? baseType) => string.Format( GetString("WeakBaseType", nameof(entityType), nameof(baseType)), @@ -2694,6 +2710,7 @@ public static string WeakBaseType([CanBeNull] object? entityType, [CanBeNull] ob /// /// The entity type '{entityType}' cannot have a base type because it has a defining navigation. /// + [Obsolete] public static string WeakDerivedType([CanBeNull] object? entityType) => string.Format( GetString("WeakDerivedType", nameof(entityType)), @@ -3560,6 +3577,7 @@ public static EventDefinition LogNavigationLazyLoading([NotNull] /// /// The navigation '{targetEntityType}.{inverseNavigation}' specified in the [InverseProperty] attribute cannot be used as the inverse of '{weakEntityType}.{navigation}' because it's not the defining navigation '{definingNavigation}'. /// + [Obsolete] public static EventDefinition LogNonDefiningInverseNavigation([NotNull] IDiagnosticsLogger logger) { var definition = ((LoggingDefinitions)logger.Definitions).LogNonDefiningInverseNavigation; diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 13fdfbb1e05..10b2ae9b4b0 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -124,7 +124,7 @@ Cannot add an entity type with type '{typeName}' to the model as it is a dynamically-generated proxy type. - The entity type '{entityType}' has a defining navigation and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. + The entity type '{entityType}' uses a shared type and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as having a required dependent since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. @@ -147,6 +147,9 @@ The service property '{property}' of type '{serviceType}' cannot be added to the entity type '{entityType}' because there is another property of the same type. Ignore one of the properties using the [NotMapped] attribute or 'EntityTypeBuilder.Ignore' in 'OnModelCreating'. + + The shared type entity type '{entityType}' cannot be added to the model because its name is the same as the CLR type name. This usually indicates an error, either add it as a non-shared entity type or choose a different name. + The annotation '{annotation}' was not found. Ensure that the annotation has been added to the object {annotatable} @@ -231,6 +234,7 @@ The entity type '{entityType}' with a defining navigation cannot be added to the model because an entity type with the same name already exists. + Obsolete The type '{entityType}' cannot be configured as non-owned because an owned entity type with the same name already exists. @@ -240,6 +244,7 @@ The entity type '{entityType}' cannot be added to the model because an entity type with a defining navigation with the same name already exists. + Obsolete The client projection contains a reference to a constant expression of '{constantType}' which is being passed as an argument to the method '{methodName}'. This could potentially cause a memory leak; consider assigning this constant to a local variable and using the variable in the query instead. See https://go.microsoft.com/fwlink/?linkid=2103067 for more information. @@ -471,6 +476,7 @@ The foreign keys on entity type '{dependentType}' cannot target the same entity type because it has a defining navigation. + Obsolete The types of the properties specified for the foreign key {foreignKeyProperties} on entity type '{dependentType}' do not match the types of the properties in the principal key {principalKeyProperties} on entity type '{principalType}'. Provide properties that use the same types in the same order. @@ -527,6 +533,7 @@ The entity type '{ownedEntityType}' is configured as owned, but the entity type '{nonOwnedEntityType}' is not. Configure all entity types with defining navigations sharing a CLR type as owned in 'OnModelCreating'. + Obsolete The specified index properties {indexProperties} are not declared on the entity type '{entityType}'. Ensure that index properties are declared on the target entity type. @@ -602,6 +609,7 @@ Cannot create a DbSet for '{typeName}' because it is mapped to multiple entity types with defining navigations and should be accessed through the owning entities. + Obsolete Invalid {name}: {value} @@ -801,7 +809,7 @@ The navigation '{targetEntityType}.{inverseNavigation}' specified in the [InverseProperty] attribute cannot be used as the inverse of '{weakEntityType}.{navigation}' because it's not the defining navigation '{definingNavigation}'. - Warning CoreEventId.NonDefiningInverseNavigationWarning string string string string string + Obsolete Warning CoreEventId.NonDefiningInverseNavigationWarning string string string string string The navigation '{navigation}' is non-nullable, causing the entity type '{entityType}' to be configured as the dependent side in the corresponding relationship. @@ -1039,6 +1047,7 @@ The navigation '{navigation}' used to define the entity type '{entityType}' is not present on '{definingEntityType}'. + Obsolete Cannot set the discriminator value for entity type '{entityType}' because the root entity type '{rootEntityType}' doesn't have a discriminator property configured. @@ -1082,6 +1091,7 @@ The entity type '{2_entityType}' owned by '{0_ownershipNavigation}' should use defining navigation '{1_definingNavigation}' for . + Obsolete The DbContextOptions passed to the {contextType} constructor must be a DbContextOptions<{contextType}>. When registering multiple DbContext types, make sure that the constructor for each context type has a DbContextOptions<TContext> parameter rather than a non-generic DbContextOptions parameter. @@ -1423,7 +1433,7 @@ An unnamed index specified via [Index] attribute on the entity type '{entityType}' references properties {indexProperties}, but no property with name '{propertyName}' exists on that entity type or any of its base types. - The entity type '{entityType}' has a defining navigation and the supplied entity is currently not being tracked. To start tracking this entity, call '{referenceCall}' or '{collectionCall}' on the owner entry. + The entity type '{entityType}' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '{referenceCall}' or '{collectionCall}' on the owner entry. The value for property '{1_entityType}.{0_property}' cannot be set to null because its type is '{propertyType}' which is not a nullable type. @@ -1439,9 +1449,11 @@ The type '{entityType}' cannot have entity type '{baseType}' as the base type because the latter has a defining navigation. + Obsolete The entity type '{entityType}' cannot have a base type because it has a defining navigation. + Obsolete Property '{1_entityType}.{0_property}' is of type '{actualType}' but the generic type provided is of type '{genericType}'. diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index b106a121cb1..dec14dd801c 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -151,8 +151,7 @@ protected Expression ExpandNavigation( bool derivedTypeConversion) { var targetType = navigation.TargetEntityType; - if (targetType.HasDefiningNavigation() - || targetType.IsOwned()) + if (targetType.IsOwned()) { if (entityReference.ForeignKeyExpansionMap.TryGetValue( (navigation.ForeignKey, navigation.IsOnDependent), out var ownedExpansion)) @@ -344,7 +343,7 @@ private Expression ExpandForeignKey( var collection = !foreignKey.IsUnique && !onDependent; var targetType = onDependent ? foreignKey.PrincipalEntityType : foreignKey.DeclaringEntityType; - Debug.Assert(!targetType.HasDefiningNavigation() && !targetType.IsOwned(), "Owned entity expanding foreign key."); + Debug.Assert(!targetType.IsOwned(), "Owned entity expanding foreign key."); var innerQueryable = new QueryRootExpression(targetType); var innerSource = (NavigationExpansionExpression)_navigationExpandingExpressionVisitor.Visit(innerQueryable); diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 0a1d9339115..909b77a931d 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -2201,7 +2201,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() } [ConditionalFact] - public virtual void Weak_owned_types_are_stored_in_snapshot() + public virtual void Shared_owned_types_are_stored_in_snapshot() { Test( builder => @@ -2431,8 +2431,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) model => { Assert.Equal(2, model.GetEntityTypes().Count()); + var testOwner = model.FindEntityType( + "Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"); var testOwnee = model.FindEntityType( - "Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwnee"); + "Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwnee", "OwnedEntities", testOwner); Assert.NotNull(testOwnee.FindCheckConstraint("CK_TestOwnee_TestEnum_Enum_Constraint")); }); } diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryFixture.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryFixture.cs similarity index 77% rename from test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryFixture.cs rename to test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryFixture.cs index ea161caa181..e4fe89dc95c 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryFixture.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsWeakQueryInMemoryFixture : ComplexNavigationsWeakQueryFixtureBase + public class ComplexNavigationsSharedTypeQueryInMemoryFixture : ComplexNavigationsSharedTypeQueryFixtureBase { protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; diff --git a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryTest.cs similarity index 92% rename from test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs rename to test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryTest.cs index 4582a266ecc..c1ccee85fd1 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsWeakQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/ComplexNavigationsSharedTypeQueryInMemoryTest.cs @@ -7,12 +7,12 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsWeakQueryInMemoryTest : - ComplexNavigationsWeakQueryTestBase + public class ComplexNavigationsSharedTypeQueryInMemoryTest : + ComplexNavigationsSharedTypeQueryTestBase { // ReSharper disable once UnusedParameter.Local - public ComplexNavigationsWeakQueryInMemoryTest( - ComplexNavigationsWeakQueryInMemoryFixture fixture, + public ComplexNavigationsSharedTypeQueryInMemoryTest( + ComplexNavigationsSharedTypeQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { diff --git a/test/EFCore.Proxies.Tests/ProxyTests.cs b/test/EFCore.Proxies.Tests/ProxyTests.cs index d926dbdf97a..a33b7aa7a0c 100644 --- a/test/EFCore.Proxies.Tests/ProxyTests.cs +++ b/test/EFCore.Proxies.Tests/ProxyTests.cs @@ -109,24 +109,6 @@ public void CreateProxy_works_for_owned_but_not_weak_entity_types() Assert.Same(typeof(IsOwnedButNotWeak), context.CreateProxy(typeof(IsOwnedButNotWeak)).GetType().BaseType); } - [ConditionalFact] // Issue #22407 - public void CreateProxy_throws_for_weak_entity_types() - { - using var context = new NeweyContext(); - - Assert.Equal( - ProxiesStrings.EntityTypeNotFoundWeak(nameof(IsWeak)), - Assert.Throws(() => context.CreateProxy()).Message); - - Assert.Equal( - ProxiesStrings.EntityTypeNotFoundWeak(nameof(IsWeak)), - Assert.Throws(() => context.CreateProxy(_ => { })).Message); - - Assert.Equal( - ProxiesStrings.EntityTypeNotFoundWeak(nameof(IsWeak)), - Assert.Throws(() => context.CreateProxy(typeof(IsWeak))).Message); - } - [ConditionalFact] public void CreateProxy_uses_parameterless_constructor() { diff --git a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedQueryTypeRelationalTestBase.cs similarity index 98% rename from test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalTestBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedQueryTypeRelationalTestBase.cs index 6bafa6ed1e2..e5012931e94 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedQueryTypeRelationalTestBase.cs @@ -10,10 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query { - public abstract class ComplexNavigationsWeakQueryRelationalTestBase : ComplexNavigationsWeakQueryTestBase - where TFixture : ComplexNavigationsWeakQueryRelationalFixtureBase, new() + public abstract class ComplexNavigationsSharedQueryTypeRelationalTestBase : ComplexNavigationsSharedTypeQueryTestBase + where TFixture : ComplexNavigationsSharedTypeQueryRelationalFixtureBase, new() { - protected ComplexNavigationsWeakQueryRelationalTestBase(TFixture fixture) + protected ComplexNavigationsSharedQueryTypeRelationalTestBase(TFixture fixture) : base(fixture) { } diff --git a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryRelationalFixtureBase.cs similarity index 91% rename from test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalFixtureBase.cs rename to test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryRelationalFixtureBase.cs index 87de2b70043..268166f4ba4 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsWeakQueryRelationalFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryRelationalFixtureBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public abstract class ComplexNavigationsWeakQueryRelationalFixtureBase : ComplexNavigationsWeakQueryFixtureBase + public abstract class ComplexNavigationsSharedTypeQueryRelationalFixtureBase : ComplexNavigationsSharedTypeQueryFixtureBase { public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 7e5544053c5..21a4d03e87b 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -117,28 +117,6 @@ public void Can_get_and_set_schema_name_on_entity_type() Assert.Null(entityType.GetSchema()); } - [ConditionalFact] - public void Can_get_table_and_schema_name_for_non_owned_entity_types_with_defining_navigation() - { - var modelBuilder = new ModelBuilder(); - - var orderType = modelBuilder - .Entity() - .Metadata; - - var customerType = modelBuilder.Model.AddEntityType(typeof(Customer), nameof(Order.Customer), orderType); - - Assert.Equal("Order_Customer", customerType.GetTableName()); - - orderType.SetTableName(null); - - Assert.Equal("Customer_Customer", customerType.GetTableName()); - - customerType.SetTableName("Customizer"); - - Assert.Equal("Customizer", customerType.GetTableName()); - } - [ConditionalFact] public void Gets_model_schema_if_schema_on_entity_type_not_set() { diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 2e4992a72cb..1a8a676b243 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -158,7 +158,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) Assert.Equal( new[] { - nameof(Order), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(OrderDetails) + nameof(Order), nameof(OrderDetails), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address" }, ordersView.EntityTypeMappings.Select(m => m.EntityType.DisplayName())); Assert.Equal( @@ -273,7 +273,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal( new[] { - nameof(Order), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(OrderDetails) + nameof(Order), nameof(OrderDetails), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address" }, ordersTable.EntityTypeMappings.Select(m => m.EntityType.DisplayName())); Assert.Equal( diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryFixtureBase.cs similarity index 99% rename from test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs rename to test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryFixtureBase.cs index 887bb217528..1eb51222445 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryFixtureBase.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -12,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public abstract class ComplexNavigationsWeakQueryFixtureBase : ComplexNavigationsQueryFixtureBase, IQueryFixtureBase + public abstract class ComplexNavigationsSharedTypeQueryFixtureBase : ComplexNavigationsQueryFixtureBase, IQueryFixtureBase { protected override string StoreName { get; } = "ComplexNavigationsOwned"; diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryTestBase.cs similarity index 84% rename from test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs rename to test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryTestBase.cs index 0793f76b94e..2479bc610e2 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsSharedTypeQueryTestBase.cs @@ -6,32 +6,14 @@ namespace Microsoft.EntityFrameworkCore.Query { - public abstract class ComplexNavigationsWeakQueryTestBase : ComplexNavigationsQueryTestBase - where TFixture : ComplexNavigationsWeakQueryFixtureBase, new() + public abstract class ComplexNavigationsSharedTypeQueryTestBase : ComplexNavigationsQueryTestBase + where TFixture : ComplexNavigationsSharedTypeQueryFixtureBase, new() { - protected ComplexNavigationsWeakQueryTestBase(TFixture fixture) + protected ComplexNavigationsSharedTypeQueryTestBase(TFixture fixture) : base(fixture) { } - // Naked instances not supported - public override Task Entity_equality_empty(bool async) - { - return Task.CompletedTask; - } - - public override Task Key_equality_two_conditions_on_same_navigation(bool async) - { - return Task.CompletedTask; - } - - public override Task Level4_Include(bool async) - { - // Due to level 4 being weak, other tests using l4 as root could cause same query as this one to run - // generating different SQL - return Task.CompletedTask; - } - // Self-ref not supported public override Task Join_navigation_self_ref(bool async) { @@ -53,16 +35,6 @@ public override Task Join_condition_optimizations_applied_correctly_when_anonymo return Task.CompletedTask; } - public override Task Include_after_multiple_SelectMany_and_reference_navigation(bool async) - { - return Task.CompletedTask; - } - - public override Task Include_after_SelectMany_and_multiple_reference_navigations(bool async) - { - return Task.CompletedTask; - } - [ConditionalTheory(Skip = "issue #13560")] public override Task Multiple_SelectMany_with_nested_navigations_and_explicit_DefaultIfEmpty_joined_together(bool async) { diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index a5fe74eb331..abe1e8b55eb 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -187,17 +187,6 @@ public virtual async Task Set_throws_for_owned_type(bool async) exception.Message); } - [ConditionalTheory] - [MemberData(nameof(IsAsyncData))] - public virtual async Task Set_throws_for_owned_type_with_defining_navigation(bool async) - { - var exception = await Assert.ThrowsAsync(() => AssertQuery(async, ss => ss.Set())); - - Assert.Equal( - CoreStrings.InvalidSetTypeWeak(nameof(OwnedAddress)), - exception.Message); - } - [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Navigation_rewrite_on_owned_reference_followed_by_regular_entity(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerFixture.cs similarity index 76% rename from test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerFixture.cs rename to test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerFixture.cs index 7e41954e257..24e1263b7be 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsWeakQuerySqlServerFixture : ComplexNavigationsWeakQueryRelationalFixtureBase + public class ComplexNavigationsSharedTypeQuerySqlServerFixture : ComplexNavigationsSharedTypeQueryRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs new file mode 100644 index 00000000000..70f2dd76309 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -0,0 +1,293 @@ +// 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.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.EntityFrameworkCore.Query +{ + public class ComplexNavigationsSharedTypeQuerySqlServerTest : ComplexNavigationsSharedQueryTypeRelationalTestBase< + ComplexNavigationsSharedTypeQuerySqlServerFixture> + { + public ComplexNavigationsSharedTypeQuerySqlServerTest( + ComplexNavigationsSharedTypeQuerySqlServerFixture fixture, + ITestOutputHelper testOutputHelper) + : base(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + public override async Task Simple_level1_include(bool async) + { + await base.Simple_level1_include(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[Id]"); + } + + public override async Task Simple_level1(bool async) + { + await base.Simple_level1(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name] +FROM [Level1] AS [l]"); + } + + public override async Task Simple_level1_level2_include(bool async) + { + await base.Simple_level1_level2_include(async); + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t1].[Id], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Level3_Name], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[Id] +LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id] + FROM [Level1] AS [l2] + INNER JOIN ( + SELECT [l3].[Id] + FROM [Level1] AS [l3] + INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] + WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t0] ON [l2].[Id] = [t0].[Id] + WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL +) AS [t1] ON [t].[Id] = [t1].[Id]"); + } + + public override async Task Simple_level1_level2_GroupBy_Count(bool async) + { + await base.Simple_level1_level2_GroupBy_Count(async); + + AssertSql( + @"SELECT COUNT(*) +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [l0].[Id] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[Id] +LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level3_Name] + FROM [Level1] AS [l2] + INNER JOIN ( + SELECT [l3].[Id] + FROM [Level1] AS [l3] + INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] + WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t0] ON [l2].[Id] = [t0].[Id] + WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL +) AS [t1] ON [t].[Id] = [t1].[Id] +GROUP BY [t1].[Level3_Name]"); + } + + public override async Task Simple_level1_level2_GroupBy_Having_Count(bool async) + { + await base.Simple_level1_level2_GroupBy_Having_Count(async); + + AssertSql( + @"SELECT COUNT(*) +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [l0].[Id] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[Id] +LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level3_Name] + FROM [Level1] AS [l2] + INNER JOIN ( + SELECT [l3].[Id] + FROM [Level1] AS [l3] + INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] + WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t0] ON [l2].[Id] = [t0].[Id] + WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL +) AS [t1] ON [t].[Id] = [t1].[Id] +GROUP BY [t1].[Level3_Name] +HAVING MIN(COALESCE([t].[Id], 0)) > 0"); + } + + public override async Task Simple_level1_level2_level3_include(bool async) + { + await base.Simple_level1_level2_level3_include(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t1].[Id], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Level3_Name], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t4].[Id], [t4].[Level3_Optional_Id], [t4].[Level3_Required_Id], [t4].[Level4_Name], [t4].[OneToMany_Optional_Inverse4Id], [t4].[OneToMany_Required_Inverse4Id], [t4].[OneToOne_Optional_PK_Inverse4Id] +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[Id] +LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id] + FROM [Level1] AS [l2] + INNER JOIN ( + SELECT [l3].[Id] + FROM [Level1] AS [l3] + INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] + WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t0] ON [l2].[Id] = [t0].[Id] + WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL +) AS [t1] ON [t].[Id] = [t1].[Id] +LEFT JOIN ( + SELECT [l5].[Id], [l5].[Level3_Optional_Id], [l5].[Level3_Required_Id], [l5].[Level4_Name], [l5].[OneToMany_Optional_Inverse4Id], [l5].[OneToMany_Required_Inverse4Id], [l5].[OneToOne_Optional_PK_Inverse4Id] + FROM [Level1] AS [l5] + INNER JOIN ( + SELECT [l6].[Id] + FROM [Level1] AS [l6] + INNER JOIN ( + SELECT [l7].[Id] + FROM [Level1] AS [l7] + INNER JOIN [Level1] AS [l8] ON [l7].[Id] = [l8].[Id] + WHERE [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t2] ON [l6].[Id] = [t2].[Id] + WHERE [l6].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l6].[Level2_Required_Id] IS NOT NULL + ) AS [t3] ON [l5].[Id] = [t3].[Id] + WHERE [l5].[OneToMany_Required_Inverse4Id] IS NOT NULL AND [l5].[Level3_Required_Id] IS NOT NULL +) AS [t4] ON [t1].[Id] = [t4].[Id]"); + } + + public override async Task Nested_group_join_with_take(bool async) + { + await base.Nested_group_join_with_take(async); + + AssertSql( + @"@__p_0='2' + +SELECT [t3].[Level2_Name] +FROM ( + SELECT TOP(@__p_0) [l].[Id], [t0].[Id0] AS [Id00] + FROM [Level1] AS [l] + LEFT JOIN ( + SELECT [t].[Id] AS [Id0], [t].[Level1_Optional_Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] + WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t] ON [l0].[Id] = [t].[Id] + WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] + ORDER BY [l].[Id] +) AS [t1] +LEFT JOIN ( + SELECT [t2].[Level1_Optional_Id], [t2].[Level2_Name] + FROM [Level1] AS [l3] + LEFT JOIN ( + SELECT [l4].[Id], [l4].[OneToOne_Required_PK_Date], [l4].[Level1_Optional_Id], [l4].[Level1_Required_Id], [l4].[Level2_Name], [l4].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l4] + INNER JOIN [Level1] AS [l5] ON [l4].[Id] = [l5].[Id] + WHERE [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t2] ON [l3].[Id] = [t2].[Id] + WHERE ([t2].[OneToOne_Required_PK_Date] IS NOT NULL AND [t2].[Level1_Required_Id] IS NOT NULL) AND [t2].[OneToMany_Required_Inverse2Id] IS NOT NULL +) AS [t3] ON [t1].[Id00] = [t3].[Level1_Optional_Id] +ORDER BY [t1].[Id]"); + } + + public override async Task Explicit_GroupJoin_in_subquery_with_unrelated_projection2(bool async) + { + await base.Explicit_GroupJoin_in_subquery_with_unrelated_projection2(async); + + AssertSql( + @"SELECT [t1].[Id] +FROM ( + SELECT DISTINCT [l].[Id], [l].[Date], [l].[Name] + FROM [Level1] AS [l] + LEFT JOIN ( + SELECT [t].[Level1_Optional_Id], [t].[Level2_Name] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Level2_Name], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] + WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t] ON [l0].[Id] = [t].[Id] + WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id] + WHERE ([t0].[Level2_Name] <> N'Foo') OR [t0].[Level2_Name] IS NULL +) AS [t1]"); + } + + public override async Task Result_operator_nav_prop_reference_optional_via_DefaultIfEmpty(bool async) + { + await base.Result_operator_nav_prop_reference_optional_via_DefaultIfEmpty(async); + + AssertSql( + @"SELECT COALESCE(SUM(CASE + WHEN ([t0].[OneToOne_Required_PK_Date] IS NULL OR [t0].[Level1_Required_Id] IS NULL) OR [t0].[OneToMany_Required_Inverse2Id] IS NULL THEN 0 + ELSE [t0].[Level1_Required_Id] +END), 0) +FROM [Level1] AS [l] +LEFT JOIN ( + SELECT [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + INNER JOIN [Level1] AS [l2] ON [l1].[Id] = [l2].[Id] + WHERE [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t] ON [l0].[Id] = [t].[Id] + WHERE ([t].[OneToOne_Required_PK_Date] IS NOT NULL AND [t].[Level1_Required_Id] IS NOT NULL) AND [t].[OneToMany_Required_Inverse2Id] IS NOT NULL +) AS [t0] ON [l].[Id] = [t0].[Level1_Optional_Id]"); + } + + public override async Task SelectMany_with_Include1(bool async) + { + await base.SelectMany_with_Include1(async); + + AssertSql( + @"SELECT [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [l].[Id], [t].[Id0], [t1].[Id], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Level3_Name], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[Id0], [t1].[Id00] +FROM [Level1] AS [l] +INNER JOIN ( + SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l1].[Id] AS [Id0] + FROM [Level1] AS [l0] + INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] + WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) +) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] +LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id], [t0].[Id] AS [Id0], [t0].[Id0] AS [Id00] + FROM [Level1] AS [l2] + INNER JOIN ( + SELECT [l3].[Id], [l4].[Id] AS [Id0] + FROM [Level1] AS [l3] + INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] + WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) + ) AS [t0] ON [l2].[Id] = [t0].[Id] + WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL +) AS [t1] ON [t].[Id] = [t1].[OneToMany_Optional_Inverse3Id] +ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0], [t1].[Id00]"); + } + + public override async Task SelectMany_with_navigation_and_Distinct(bool async) + { + var message = (await Assert.ThrowsAsync( + () => base.SelectMany_with_navigation_and_Distinct(async))).Message; + + Assert.Equal(RelationalStrings.InsufficientInformationToIdentifyOuterElementOfCollectionJoin, message); + } + + private void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs deleted file mode 100644 index efeb76d3d0b..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsWeakQuerySqlServerTest.cs +++ /dev/null @@ -1,180 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class ComplexNavigationsWeakQuerySqlServerTest : ComplexNavigationsWeakQueryRelationalTestBase< - ComplexNavigationsWeakQuerySqlServerFixture> - { - public ComplexNavigationsWeakQuerySqlServerTest( - ComplexNavigationsWeakQuerySqlServerFixture fixture, - ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - - public override async Task Simple_level1_include(bool async) - { - await base.Simple_level1_include(async); - - AssertSql( - @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToOne_Required_PK_Date], [l].[Level1_Optional_Id], [l].[Level1_Required_Id], [l].[Level2_Name], [l].[OneToMany_Optional_Inverse2Id], [l].[OneToMany_Required_Inverse2Id], [l].[OneToOne_Optional_PK_Inverse2Id] -FROM [Level1] AS [l]"); - } - - public override async Task Simple_level1(bool async) - { - await base.Simple_level1(async); - - AssertSql( - @"SELECT [l].[Id], [l].[Date], [l].[Name] -FROM [Level1] AS [l]"); - } - - public override async Task Simple_level1_level2_include(bool async) - { - await base.Simple_level1_level2_include(async); - - AssertSql( - @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToOne_Required_PK_Date], [l].[Level1_Optional_Id], [l].[Level1_Required_Id], [l].[Level2_Name], [l].[OneToMany_Optional_Inverse2Id], [l].[OneToMany_Required_Inverse2Id], [l].[OneToOne_Optional_PK_Inverse2Id], [l].[Level2_Optional_Id], [l].[Level2_Required_Id], [l].[Level3_Name], [l].[OneToMany_Optional_Inverse3Id], [l].[OneToMany_Required_Inverse3Id], [l].[OneToOne_Optional_PK_Inverse3Id] -FROM [Level1] AS [l]"); - } - - public override async Task Simple_level1_level2_GroupBy_Count(bool async) - { - await base.Simple_level1_level2_GroupBy_Count(async); - - AssertSql( - @"SELECT COUNT(*) -FROM [Level1] AS [l] -GROUP BY [l].[Level3_Name]"); - } - - public override async Task Simple_level1_level2_GroupBy_Having_Count(bool async) - { - await base.Simple_level1_level2_GroupBy_Having_Count(async); - - AssertSql( - @"SELECT COUNT(*) -FROM [Level1] AS [l] -GROUP BY [l].[Level3_Name] -HAVING MIN(COALESCE([l].[Id], 0)) > 0"); - } - - public override async Task Simple_level1_level2_level3_include(bool async) - { - await base.Simple_level1_level2_level3_include(async); - - AssertSql( - @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToOne_Required_PK_Date], [l].[Level1_Optional_Id], [l].[Level1_Required_Id], [l].[Level2_Name], [l].[OneToMany_Optional_Inverse2Id], [l].[OneToMany_Required_Inverse2Id], [l].[OneToOne_Optional_PK_Inverse2Id], [l].[Level2_Optional_Id], [l].[Level2_Required_Id], [l].[Level3_Name], [l].[OneToMany_Optional_Inverse3Id], [l].[OneToMany_Required_Inverse3Id], [l].[OneToOne_Optional_PK_Inverse3Id], [l].[Level3_Optional_Id], [l].[Level3_Required_Id], [l].[Level4_Name], [l].[OneToMany_Optional_Inverse4Id], [l].[OneToMany_Required_Inverse4Id], [l].[OneToOne_Optional_PK_Inverse4Id] -FROM [Level1] AS [l]"); - } - - public override async Task Nested_group_join_with_take(bool async) - { - await base.Nested_group_join_with_take(async); - - AssertSql( - @"@__p_0='2' - -SELECT [t1].[Level2_Name] -FROM ( - SELECT TOP(@__p_0) [l].[Id], [t].[Id0] AS [Id00] - FROM [Level1] AS [l] - LEFT JOIN ( - SELECT [l0].[Level1_Optional_Id], [l0].[Id] AS [Id0] - FROM [Level1] AS [l0] - WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] - ORDER BY [l].[Id] -) AS [t0] -LEFT JOIN ( - SELECT [l1].[Level1_Optional_Id], [l1].[Level2_Name] - FROM [Level1] AS [l1] - WHERE ([l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL) AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL -) AS [t1] ON [t0].[Id00] = [t1].[Level1_Optional_Id] -ORDER BY [t0].[Id]"); - } - - public override async Task Explicit_GroupJoin_in_subquery_with_unrelated_projection2(bool async) - { - await base.Explicit_GroupJoin_in_subquery_with_unrelated_projection2(async); - - AssertSql( - @"SELECT [t0].[Id] -FROM ( - SELECT DISTINCT [l].[Id], [l].[Date], [l].[Name] - FROM [Level1] AS [l] - LEFT JOIN ( - SELECT [l0].[Level1_Optional_Id], [l0].[Level2_Name] - FROM [Level1] AS [l0] - WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id] - WHERE ([t].[Level2_Name] <> N'Foo') OR [t].[Level2_Name] IS NULL -) AS [t0]"); - } - - public override async Task Result_operator_nav_prop_reference_optional_via_DefaultIfEmpty(bool async) - { - await base.Result_operator_nav_prop_reference_optional_via_DefaultIfEmpty(async); - - AssertSql( - @"SELECT COALESCE(SUM(CASE - WHEN ([t].[OneToOne_Required_PK_Date] IS NULL OR [t].[Level1_Required_Id] IS NULL) OR [t].[OneToMany_Required_Inverse2Id] IS NULL THEN 0 - ELSE [t].[Level1_Required_Id] -END), 0) -FROM [Level1] AS [l] -LEFT JOIN ( - SELECT [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l0] - WHERE ([l0].[OneToOne_Required_PK_Date] IS NOT NULL AND [l0].[Level1_Required_Id] IS NOT NULL) AND [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL -) AS [t] ON [l].[Id] = [t].[Level1_Optional_Id]"); - } - - public override async Task SelectMany_with_Include1(bool async) - { - await base.SelectMany_with_Include1(async); - - AssertSql( - @"SELECT [t].[Id], [t].[OneToOne_Required_PK_Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Level2_Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [l].[Id], [t].[Id0], [t1].[Id], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Level3_Name], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[Id0], [t1].[Id00] -FROM [Level1] AS [l] -INNER JOIN ( - SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Level2_Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l1].[Id] AS [Id0] - FROM [Level1] AS [l0] - INNER JOIN [Level1] AS [l1] ON [l0].[Id] = [l1].[Id] - WHERE [l0].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l0].[Level1_Required_Id] IS NOT NULL AND [l0].[OneToOne_Required_PK_Date] IS NOT NULL) -) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] -LEFT JOIN ( - SELECT [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Level3_Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id], [t0].[Id] AS [Id0], [t0].[Id0] AS [Id00] - FROM [Level1] AS [l2] - INNER JOIN ( - SELECT [l3].[Id], [l4].[Id] AS [Id0] - FROM [Level1] AS [l3] - INNER JOIN [Level1] AS [l4] ON [l3].[Id] = [l4].[Id] - WHERE [l3].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l3].[Level1_Required_Id] IS NOT NULL AND [l3].[OneToOne_Required_PK_Date] IS NOT NULL) - ) AS [t0] ON [l2].[Id] = [t0].[Id] - WHERE [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL AND [l2].[Level2_Required_Id] IS NOT NULL -) AS [t1] ON [t].[Id] = [t1].[OneToMany_Optional_Inverse3Id] -ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0], [t1].[Id00]"); - } - - public override async Task SelectMany_with_navigation_and_Distinct(bool async) - { - var message = (await Assert.ThrowsAsync( - () => base.SelectMany_with_navigation_and_Distinct(async))).Message; - - Assert.Equal(RelationalStrings.InsufficientInformationToIdentifyOuterElementOfCollectionJoin, message); - } - - private void AssertSql(params string[] expected) - => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); - } -} diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteFixture.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteFixture.cs similarity index 76% rename from test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteFixture.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteFixture.cs index 79746dc27a7..b82031a326e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteFixture.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsWeakQuerySqliteFixture : ComplexNavigationsWeakQueryRelationalFixtureBase + public class ComplexNavigationsSharedTypeQuerySqliteFixture : ComplexNavigationsSharedTypeQueryRelationalFixtureBase { protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteTest.cs similarity index 70% rename from test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs rename to test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteTest.cs index a8beeb32908..715f67baf6a 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqliteTest.cs @@ -10,10 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Query { - public class ComplexNavigationsWeakQuerySqliteTest : ComplexNavigationsWeakQueryRelationalTestBase< - ComplexNavigationsWeakQuerySqliteFixture> + public class ComplexNavigationsSharedTypeQuerySqliteTest : ComplexNavigationsSharedQueryTypeRelationalTestBase< + ComplexNavigationsSharedTypeQuerySqliteFixture> { - public ComplexNavigationsWeakQuerySqliteTest(ComplexNavigationsWeakQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) + public ComplexNavigationsSharedTypeQuerySqliteTest(ComplexNavigationsSharedTypeQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) : base(fixture) { } @@ -74,5 +74,23 @@ public override async Task Complex_query_with_let_collection_projection_FirstOrD SqliteStrings.ApplyNotSupported, (await Assert.ThrowsAsync( () => base.Complex_query_with_let_collection_projection_FirstOrDefault(async))).Message); + + public override async Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(async))).Message); + + public override async Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only_split(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only_split(async))).Message); + + public override async Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(async))).Message); } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs index 3d923f4a913..58a962e0b08 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs @@ -118,7 +118,7 @@ public void Can_get_owned_entity_entry() Assert.Equal( CoreStrings.UntrackedDependentEntity( - typeof(ChildPN).ShortDisplayName(), + nameof(ChildPN), ".Reference().TargetEntry", ".Collection().FindEntry()"), Assert.Throws(() => context.Entry(dependent)).Message); @@ -132,7 +132,7 @@ public void Can_get_owned_entity_entry() Assert.NotNull(dependentEntry2); Assert.Equal( CoreStrings.AmbiguousDependentEntity( - typeof(ChildPN).ShortDisplayName(), + nameof(ChildPN), "." + nameof(EntityEntry.Reference) + "()." + nameof(ReferenceEntry.TargetEntry)), Assert.Throws(() => context.Entry(dependent)).Message); } @@ -155,8 +155,8 @@ public void Adding_duplicate_owned_entity_throws_by_default() CoreStrings.WarningAsErrorTemplate( CoreEventId.DuplicateDependentEntityTypeInstanceWarning.ToString(), CoreResources.LogDuplicateDependentEntityTypeInstance(new TestLogger()).GenerateMessage( - typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + typeof(ChildPN).ShortDisplayName(), - typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + typeof(ChildPN).ShortDisplayName()), + typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN), + typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN)), "CoreEventId.DuplicateDependentEntityTypeInstanceWarning"), Assert.Throws(() => context.Entry(principal).Reference(p => p.Child2).TargetEntry).Message); } @@ -228,13 +228,13 @@ public void Add_principal_with_dependent_unidirectional_nav(EntityState entitySt var dependentEntry = context.Entry(dependent); Assert.Equal(principal.Id, dependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, dependentEntry.State); - Assert.Equal(nameof(ParentPN.Child1), dependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependentEntry.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry.Metadata.DisplayName()); }); } @@ -309,7 +309,8 @@ public void Add_principal_with_dependent_both_navs(EntityState entityState, bool var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(SubChild), + subDependentEntry.Metadata.DisplayName()); }); } @@ -384,7 +385,8 @@ public void Add_principal_with_dependent_principal_nav(EntityState entityState, var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(Child.SubChild), + subDependentEntry.Metadata.DisplayName()); }); } @@ -519,13 +521,14 @@ public void Add_principal_with_dependent_unidirectional_nav_collection( var dependentEntry = context.Entry(dependent); Assert.Equal(principal.Id, dependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, dependentEntry.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), dependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), dependentEntry.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); }); } @@ -665,7 +668,8 @@ public void Add_principal_with_dependent_both_navs_collection( var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), + subDependentEntry.Metadata.DisplayName()); }); } @@ -805,7 +809,8 @@ public void Add_principal_with_dependent_principal_nav_collection( var subDependentEntry = context.Entry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(useTrackGraph == null ? EntityState.Added : entityState, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), + subDependentEntry.Metadata.DisplayName()); }); } @@ -871,13 +876,13 @@ public void Instance_changed_unidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal).Reference(p => p.Child2).TargetEntry; Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.Child2), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -935,14 +940,14 @@ public void Instance_changed_bidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); Assert.Same(dependent2, subDependent2.Parent); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChild), subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1033,12 +1038,12 @@ public void Instance_changed_unidirectional_collection(EntityState entityState, Assert.Equal(entityState == EntityState.Added ? EntityState.Detached : EntityState.Deleted, dependentEntry1.State); Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection2), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Equal(principal.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1130,14 +1135,14 @@ public void Instance_changed_bidirectional_collection(EntityState entityState, C Assert.Equal(entityState == EntityState.Added ? EntityState.Detached : EntityState.Deleted, dependentEntry1.State); Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Same(dependent2, subDependent2.Parent); Assert.Equal(principal.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(Child.SubChildCollection), subDependentEntry2.Metadata.DefiningNavigationName); - + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), subDependentEntry2.Metadata.DisplayName()); + context.ChangeTracker.CascadeChanges(); Assert.True(context.ChangeTracker.HasChanges()); @@ -1201,13 +1206,14 @@ public void Identity_changed_unidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal).Reference(p => p.Child2).TargetEntry; Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.Child2), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1261,14 +1267,14 @@ public void Identity_changed_bidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); Assert.Same(dependent, subDependent.Parent); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(SubChild), subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1348,13 +1354,14 @@ public void Identity_changed_unidirectional_collection(EntityState entityState, var dependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection2).FindEntry(dependent); Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection2), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); var subDependentEntry = dependentEntry2.Collection(p => p.SubChildCollection).FindEntry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1434,14 +1441,14 @@ public void Identity_changed_bidirectional_collection(EntityState entityState, C var dependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection1).FindEntry(dependent); Assert.Equal(principal.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); Assert.Same(dependent, subDependent.Parent); var subDependentEntry = dependentEntry2.Collection(p => p.SubChildCollection).FindEntry(subDependent); Assert.Equal(principal.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1501,7 +1508,7 @@ public void Identity_swapped_unidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal.Id, dependent1Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent1Entry.State); - Assert.Equal(nameof(ParentPN.Child1), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependent1Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent1Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -1509,7 +1516,7 @@ public void Identity_swapped_unidirectional(EntityState entityState) var dependent2Entry = context.Entry(principal).Reference(p => p.Child2).TargetEntry; Assert.Equal(principal.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent2Entry.State); - Assert.Equal(nameof(ParentPN.Child2), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN), dependent2Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent2Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -1518,13 +1525,13 @@ public void Identity_swapped_unidirectional(EntityState entityState) var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); var subDependentEntry2 = dependent2Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1588,7 +1595,7 @@ public void Identity_swapped_bidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal.Id, dependent1Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent1Entry.State); - Assert.Equal(nameof(Parent.Child1), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependent1Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent1Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -1596,7 +1603,7 @@ public void Identity_swapped_bidirectional(EntityState entityState) var dependent2Entry = context.Entry(principal).Reference(p => p.Child2).TargetEntry; Assert.Equal(principal.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent2Entry.State); - Assert.Equal(nameof(Parent.Child2), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child2) + "#" + nameof(Child), dependent2Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent2Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -1606,14 +1613,14 @@ public void Identity_swapped_bidirectional(EntityState entityState) var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(SubChild), subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); Assert.Same(dependent2, subDependent2.Parent); var subDependentEntry2 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(SubChild), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1696,21 +1703,21 @@ public void Identity_swapped_unidirectional_collection(EntityState entityState, principal.ChildCollection2 = principal.ChildCollection1; principal.ChildCollection1 = tempCollection; - var newDependentEntry1 = context.Entry(principal).Collection(p => p.ChildCollection2) - .FindEntry(dependent1); - newDependentEntry1.Property("Id").CurrentValue = dependentEntry1.Property("Id").CurrentValue; - - var newDependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection1) + var newDependentEntry1 = context.Entry(principal).Collection(p => p.ChildCollection1) .FindEntry(dependent2); - newDependentEntry2.Property("Id").CurrentValue = dependentEntry2.Property("Id").CurrentValue; + newDependentEntry1.Property("Id").CurrentValue = dependentEntry2.Property("Id").CurrentValue; + + var newDependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection2) + .FindEntry(dependent1); + newDependentEntry2.Property("Id").CurrentValue = dependentEntry1.Property("Id").CurrentValue; var newSubDependentEntry1 = newDependentEntry1.Collection(p => p.SubChildCollection) - .FindEntry(subDependent1); - newSubDependentEntry1.Property("Id").CurrentValue = subDependentEntry1.Property("Id").CurrentValue; + .FindEntry(subDependent2); + newSubDependentEntry1.Property("Id").CurrentValue = subDependentEntry2.Property("Id").CurrentValue; var newSubDependentEntry2 = newDependentEntry2.Collection(p => p.SubChildCollection) - .FindEntry(subDependent2); - newSubDependentEntry2.Property("Id").CurrentValue = subDependentEntry2.Property("Id").CurrentValue; + .FindEntry(subDependent1); + newSubDependentEntry2.Property("Id").CurrentValue = subDependentEntry1.Property("Id").CurrentValue; context.ChangeTracker.DetectChanges(); @@ -1721,29 +1728,29 @@ public void Identity_swapped_unidirectional_collection(EntityState entityState, Assert.Contains(principal.ChildCollection1, e => ReferenceEquals(e, dependent2)); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(principal.Id, newDependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(EntityState.Added, newDependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), newDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(principal.Id, newDependentEntry1.Property("ParentId").CurrentValue); + Assert.Equal(EntityState.Added, newDependentEntry1.State); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), newDependentEntry1.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, - newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); + newDependentEntry1.GetInfrastructure().SharedIdentityEntry?.EntityState); - Assert.Equal(principal.Id, newDependentEntry1.Property("ParentId").CurrentValue); - Assert.Equal(EntityState.Added, newDependentEntry1.State); - Assert.Equal(nameof(ParentPN.ChildCollection2), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(principal.Id, newDependentEntry2.Property("ParentId").CurrentValue); + Assert.Equal(EntityState.Added, newDependentEntry2.State); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN), newDependentEntry2.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, - newDependentEntry1.GetInfrastructure().SharedIdentityEntry?.EntityState); + newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); Assert.Contains(dependent1.SubChildCollection, e => ReferenceEquals(e, subDependent1)); - Assert.Equal(principal.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); - Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); - - Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Equal(principal.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), newSubDependentEntry2.Metadata.DisplayName()); + + Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); + Assert.Equal(principal.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); + Assert.Equal(EntityState.Added, newSubDependentEntry1.State); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), newSubDependentEntry1.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1756,8 +1763,8 @@ public void Identity_swapped_unidirectional_collection(EntityState entityState, Assert.False(context.ChangeTracker.HasChanges()); Assert.Equal(5, context.ChangeTracker.Entries().Count()); - Assert.Null(newDependentEntry2.GetInfrastructure().SharedIdentityEntry); Assert.Null(newDependentEntry1.GetInfrastructure().SharedIdentityEntry); + Assert.Null(newDependentEntry2.GetInfrastructure().SharedIdentityEntry); Assert.True(context.ChangeTracker.Entries().All(e => e.State == EntityState.Unchanged)); Assert.Contains(principal.ChildCollection2, e => ReferenceEquals(e, dependent1)); Assert.Contains(principal.ChildCollection1, e => ReferenceEquals(e, dependent2)); @@ -1825,19 +1832,19 @@ public void Identity_swapped_bidirectional_collection(EntityState entityState, C principal.ChildCollection2 = principal.ChildCollection1; principal.ChildCollection1 = tempCollection; - var newDependentEntry1 = context.Entry(principal).Collection(p => p.ChildCollection2) - .FindEntry(dependent1); - newDependentEntry1.Property("Id").CurrentValue = dependentEntry1.Property("Id").CurrentValue; - - var newDependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection1) + var newDependentEntry1 = context.Entry(principal).Collection(p => p.ChildCollection1) .FindEntry(dependent2); - newDependentEntry2.Property("Id").CurrentValue = dependentEntry2.Property("Id").CurrentValue; + newDependentEntry1.Property("Id").CurrentValue = dependentEntry2.Property("Id").CurrentValue; - var newSubDependentEntry1 = newDependentEntry1.Collection(p => p.SubChildCollection) + var newDependentEntry2 = context.Entry(principal).Collection(p => p.ChildCollection2) + .FindEntry(dependent1); + newDependentEntry2.Property("Id").CurrentValue = dependentEntry1.Property("Id").CurrentValue; + + var newSubDependentEntry1 = newDependentEntry2.Collection(p => p.SubChildCollection) .FindEntry(subDependent1); newSubDependentEntry1.Property("Id").CurrentValue = subDependentEntry1.Property("Id").CurrentValue; - var newSubDependentEntry2 = newDependentEntry2.Collection(p => p.SubChildCollection) + var newSubDependentEntry2 = newDependentEntry1.Collection(p => p.SubChildCollection) .FindEntry(subDependent2); newSubDependentEntry2.Property("Id").CurrentValue = subDependentEntry2.Property("Id").CurrentValue; @@ -1852,31 +1859,31 @@ public void Identity_swapped_bidirectional_collection(EntityState entityState, C Assert.Contains(principal.ChildCollection1, e => ReferenceEquals(e, dependent2)); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(principal.Id, newDependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(EntityState.Added, newDependentEntry2.State); - Assert.Equal(nameof(Parent.ChildCollection1), newDependentEntry2.Metadata.DefiningNavigationName); - Assert.Equal( - entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, - newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); - Assert.Equal(principal.Id, newDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newDependentEntry1.State); - Assert.Equal(nameof(Parent.ChildCollection2), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), newDependentEntry1.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, newDependentEntry1.GetInfrastructure().SharedIdentityEntry?.EntityState); + Assert.Equal(principal.Id, newDependentEntry2.Property("ParentId").CurrentValue); + Assert.Equal(EntityState.Added, newDependentEntry2.State); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection2) + "#" + nameof(Child), newDependentEntry2.Metadata.DisplayName()); + Assert.Equal( + entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, + newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); + Assert.Contains(dependent1.SubChildCollection, e => ReferenceEquals(e, subDependent1)); Assert.Same(dependent1, subDependent1.Parent); Assert.Equal(principal.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection2) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), newSubDependentEntry1.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Same(dependent2, subDependent2.Parent); Assert.Equal(principal.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), newSubDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -1889,8 +1896,8 @@ public void Identity_swapped_bidirectional_collection(EntityState entityState, C Assert.False(context.ChangeTracker.HasChanges()); Assert.Equal(5, context.ChangeTracker.Entries().Count()); - Assert.Null(newDependentEntry2.GetInfrastructure().SharedIdentityEntry); Assert.Null(newDependentEntry1.GetInfrastructure().SharedIdentityEntry); + Assert.Null(newDependentEntry2.GetInfrastructure().SharedIdentityEntry); Assert.True(context.ChangeTracker.Entries().All(e => e.State == EntityState.Unchanged)); Assert.Contains(principal.ChildCollection2, e => ReferenceEquals(e, dependent1)); Assert.Contains(principal.ChildCollection1, e => ReferenceEquals(e, dependent2)); @@ -1949,13 +1956,14 @@ public void Parent_changed_unidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2028,14 +2036,15 @@ public void Parent_changed_bidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(EntityState.Added, dependentEntry2.State); Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); Assert.Same(dependent, subDependent.Parent); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChild), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2139,14 +2148,15 @@ public void Parent_changed_unidirectional_collection(EntityState entityState, Co .FindEntry(dependent); Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); var subDependentEntry2 = dependentEntry2.Collection(p => p.SubChildCollection) .FindEntry(subDependent); Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2250,7 +2260,7 @@ public void Parent_changed_bidirectional_collection(EntityState entityState, Col .FindEntry(dependent); Assert.Equal(EntityState.Added, dependentEntry2.State); Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); Assert.Same(dependent, subDependent.Parent); @@ -2258,7 +2268,7 @@ public void Parent_changed_bidirectional_collection(EntityState entityState, Col .FindEntry(subDependent); Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(Child.SubChildCollection), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2337,24 +2347,26 @@ public void Parent_swapped_unidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal1).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal1.Id, dependent1Entry.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, dependent1Entry.State); - Assert.Equal(nameof(ParentPN.Child1), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependent1Entry.Metadata.DisplayName()); var dependent2Entry = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, dependent2Entry.State); - Assert.Equal(nameof(ParentPN.Child1), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependent2Entry.Metadata.DisplayName()); Assert.Same(subDependent1, dependent1.SubChild); var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal1.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), + subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); var subDependentEntry2 = dependent2Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), + subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2435,26 +2447,28 @@ public void Parent_swapped_bidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal1).Reference(p => p.Child1).TargetEntry; Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, dependent1Entry.State); Assert.Equal(principal1.Id, dependent1Entry.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.Child1), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependent1Entry.Metadata.DisplayName()); var dependent2Entry = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, dependent2Entry.State); - Assert.Equal(nameof(Parent.Child1), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependent2Entry.Metadata.DisplayName()); Assert.Same(subDependent1, dependent1.SubChild); Assert.Same(dependent1, subDependent1.Parent); var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal1.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(Child.SubChild), + subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); Assert.Same(dependent2, subDependent2.Parent); var subDependentEntry2 = dependent2Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(Child.SubChild), + subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2568,27 +2582,27 @@ public void Parent_swapped_unidirectional_collection(EntityState entityState, Co .FindEntry(dependent2); Assert.Equal(principal1.Id, newDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, newDependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), newDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), newDependentEntry2.Metadata.DisplayName()); var newDependentEntry1 = context.Entry(principal2).Collection(p => p.ChildCollection1) .FindEntry(dependent1); Assert.Equal(principal2.Id, newDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, newDependentEntry1.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), newDependentEntry1.Metadata.DisplayName()); Assert.Contains(dependent1.SubChildCollection, e => ReferenceEquals(e, subDependent1)); var newSubDependentEntry1 = newDependentEntry1.Collection(p => p.SubChildCollection) .FindEntry(subDependent1); Assert.Equal(principal2.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), newSubDependentEntry1.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); var newSubDependentEntry2 = newDependentEntry2.Collection(p => p.SubChildCollection) .FindEntry(subDependent2); Assert.Equal(principal1.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), newSubDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2702,13 +2716,13 @@ public void Parent_swapped_bidirectional_collection(EntityState entityState, Col .FindEntry(dependent2); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, newDependentEntry2.State); Assert.Equal(principal1.Id, newDependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.ChildCollection1), newDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), newDependentEntry2.Metadata.DisplayName()); var newDependentEntry1 = context.Entry(principal2).Collection(p => p.ChildCollection1) .FindEntry(dependent1); Assert.Equal(principal2.Id, newDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, newDependentEntry1.State); - Assert.Equal(nameof(Parent.ChildCollection1), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), newDependentEntry1.Metadata.DisplayName()); Assert.Contains(dependent1.SubChildCollection, e => ReferenceEquals(e, subDependent1)); Assert.Same(dependent1, subDependent1.Parent); @@ -2716,7 +2730,8 @@ public void Parent_swapped_bidirectional_collection(EntityState entityState, Col .FindEntry(subDependent1); Assert.Equal(principal2.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), + newSubDependentEntry1.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Same(dependent2, subDependent2.Parent); @@ -2724,7 +2739,8 @@ public void Parent_swapped_bidirectional_collection(EntityState entityState, Col .FindEntry(subDependent2); Assert.Equal(principal1.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), + newSubDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2791,13 +2807,14 @@ public void Parent_and_identity_changed_unidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2868,14 +2885,15 @@ public void Parent_and_identity_changed_bidirectional(EntityState entityState) var dependentEntry2 = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.Child1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Same(subDependent, dependent.SubChild); Assert.Same(dependent, subDependent.Parent); var subDependentEntry = dependentEntry2.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChild), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -2967,13 +2985,14 @@ public void Parent_and_identity_changed_unidirectional_collection(EntityState en var dependentEntry2 = context.Entry(principal2).Collection(p => p.ChildCollection1).FindEntry(dependent); Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); var subDependentEntry = dependentEntry2.Collection(p => p.SubChildCollection).FindEntry(subDependent); Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -3073,14 +3092,14 @@ public void Parent_and_identity_changed_bidirectional_collection(EntityState ent var dependentEntry2 = context.Entry(principal2).Collection(p => p.ChildCollection1).FindEntry(dependent); Assert.Equal(principal2.Id, dependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependentEntry2.State); - Assert.Equal(nameof(Parent.ChildCollection1), dependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), dependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent.SubChildCollection, e => ReferenceEquals(e, subDependent)); Assert.Same(dependent, subDependent.Parent); var subDependentEntry = dependentEntry2.Collection(p => p.SubChildCollection).FindEntry(subDependent); Assert.Equal(principal2.Id, subDependentEntry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry.State); - Assert.Equal(nameof(Child.SubChildCollection), subDependentEntry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(SubChild), subDependentEntry.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -3148,7 +3167,7 @@ public void Parent_and_identity_swapped_unidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal1).Reference(p => p.Child2).TargetEntry; Assert.Equal(EntityState.Added, dependent1Entry.State); Assert.Equal(principal1.Id, dependent1Entry.Property("ParentId").CurrentValue); - Assert.Equal(nameof(ParentPN.Child2), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN), dependent1Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent1Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3156,7 +3175,7 @@ public void Parent_and_identity_swapped_unidirectional(EntityState entityState) var dependent2Entry = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent2Entry.State); - Assert.Equal(nameof(ParentPN.Child1), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN), dependent2Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent2Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3165,13 +3184,13 @@ public void Parent_and_identity_swapped_unidirectional(EntityState entityState) var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal1.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); var subDependentEntry2 = dependent2Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.Child1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChild) + "#" + nameof(SubChildPN), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -3250,7 +3269,7 @@ public void Parent_and_identity_swapped_bidirectional(EntityState entityState) var dependent1Entry = context.Entry(principal1).Reference(p => p.Child2).TargetEntry; Assert.Equal(EntityState.Added, dependent1Entry.State); Assert.Equal(principal1.Id, dependent1Entry.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.Child2), dependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child2) + "#" + nameof(Child), dependent1Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent1Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3258,7 +3277,7 @@ public void Parent_and_identity_swapped_bidirectional(EntityState entityState) var dependent2Entry = context.Entry(principal2).Reference(p => p.Child1).TargetEntry; Assert.Equal(principal2.Id, dependent2Entry.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, dependent2Entry.State); - Assert.Equal(nameof(Parent.Child1), dependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child), dependent2Entry.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, dependent1Entry.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3268,14 +3287,14 @@ public void Parent_and_identity_swapped_bidirectional(EntityState entityState) var subDependentEntry1 = dependent1Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal1.Id, subDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry1.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child2) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(Child.SubChild), subDependentEntry1.Metadata.DisplayName()); Assert.Same(subDependent2, dependent2.SubChild); Assert.Same(dependent2, subDependent2.Parent); var subDependentEntry2 = dependent2Entry.Reference(p => p.SubChild).TargetEntry; Assert.Equal(principal2.Id, subDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, subDependentEntry2.State); - Assert.Equal(nameof(Child.SubChild), subDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.Child1) + "#" + nameof(Child) + "." + nameof(Child.SubChild) + "#" + nameof(Child.SubChild), subDependentEntry2.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -3396,14 +3415,14 @@ public void Parent_and_identity_swapped_unidirectional_collection(EntityState en Assert.Equal(EntityState.Added, newDependentEntry2.State); Assert.Equal(principal1.Id, newDependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(nameof(ParentPN.ChildCollection2), newDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN), newDependentEntry2.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); Assert.Equal(principal2.Id, newDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newDependentEntry1.State); - Assert.Equal(nameof(ParentPN.ChildCollection1), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN), newDependentEntry1.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, newDependentEntry1.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3411,12 +3430,14 @@ public void Parent_and_identity_swapped_unidirectional_collection(EntityState en Assert.Contains(dependent1.SubChildCollection, e => ReferenceEquals(e, subDependent1)); Assert.Equal(principal1.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection2) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + newSubDependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Equal(principal2.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(ChildPN.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(ParentPN).ShortDisplayName() + "." + nameof(ParentPN.ChildCollection1) + "#" + nameof(ChildPN) + "." + nameof(ChildPN.SubChildCollection) + "#" + nameof(SubChildPN), + newSubDependentEntry1.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); @@ -3544,14 +3565,14 @@ public void Parent_and_identity_swapped_bidirectional_collection(EntityState ent Assert.Equal(EntityState.Added, newDependentEntry2.State); Assert.Equal(principal1.Id, newDependentEntry2.Property("ParentId").CurrentValue); - Assert.Equal(nameof(Parent.ChildCollection2), newDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection2) + "#" + nameof(Child), newDependentEntry2.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); Assert.Equal(principal2.Id, newDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newDependentEntry1.State); - Assert.Equal(nameof(Parent.ChildCollection1), newDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child), newDependentEntry1.Metadata.DisplayName()); Assert.Equal( entityState == EntityState.Added ? null : (EntityState?)EntityState.Deleted, newDependentEntry2.GetInfrastructure().SharedIdentityEntry?.EntityState); @@ -3560,13 +3581,13 @@ public void Parent_and_identity_swapped_bidirectional_collection(EntityState ent Assert.Same(dependent1, subDependent1.Parent); Assert.Equal(principal1.Id, newSubDependentEntry2.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry2.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry2.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection2) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), newSubDependentEntry2.Metadata.DisplayName()); Assert.Contains(dependent2.SubChildCollection, e => ReferenceEquals(e, subDependent2)); Assert.Same(dependent2, subDependent2.Parent); Assert.Equal(principal2.Id, newSubDependentEntry1.Property("ParentId").CurrentValue); Assert.Equal(EntityState.Added, newSubDependentEntry1.State); - Assert.Equal(nameof(Child.SubChildCollection), newSubDependentEntry1.Metadata.DefiningNavigationName); + Assert.Equal(typeof(Parent).ShortDisplayName() + "." + nameof(Parent.ChildCollection1) + "#" + nameof(Child) + "." + nameof(Child.SubChildCollection) + "#" + nameof(Child.SubChild), newSubDependentEntry1.Metadata.DisplayName()); context.ChangeTracker.CascadeChanges(); diff --git a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs index 950722205ce..d3d4949c544 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore.Infrastructure; using Xunit; // ReSharper disable AccessToDisposedClosure @@ -796,11 +797,7 @@ public void Query_ownership_navigations() Seed(); using var context = new QueryFixupContext(); - // TODO: Infer these includes - // Issue #2953 var principal = context.Set() - .Include(o => o.OrderDetails.BillingAddress) - .Include(o => o.OrderDetails.ShippingAddress) .Single(); AssertFixup( @@ -830,12 +827,12 @@ public void Query_ownership_navigations() var subDependent1Entry = dependentEntry.Reference(p => p.BillingAddress).TargetEntry; Assert.Equal(principal.Id, subDependent1Entry.Property("OrderDetailsId").CurrentValue); Assert.Equal(EntityState.Unchanged, subDependent1Entry.State); - Assert.Equal(nameof(OrderDetails.BillingAddress), subDependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(OrderDetails).DisplayName() + "." + nameof(OrderDetails.BillingAddress) + "#" + typeof(Address).ShortDisplayName(), subDependent1Entry.Metadata.Name); var subDependent2Entry = dependentEntry.Reference(p => p.ShippingAddress).TargetEntry; Assert.Equal(principal.Id, subDependent2Entry.Property("OrderDetailsId").CurrentValue); Assert.Equal(EntityState.Unchanged, subDependent2Entry.State); - Assert.Equal(nameof(OrderDetails.ShippingAddress), subDependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(OrderDetails).DisplayName() + "." + nameof(OrderDetails.ShippingAddress) + "#" + typeof(Address).ShortDisplayName(), subDependent2Entry.Metadata.Name); }); } @@ -917,11 +914,11 @@ public void Query_subowned() var subDependent1Entry = context.Entry(subDependent1); Assert.Equal(principal.Id, subDependent1Entry.Property("OrderDetailsId").CurrentValue); - Assert.Equal(nameof(OrderDetails.BillingAddress), subDependent1Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(OrderDetails).DisplayName() + "." + nameof(OrderDetails.BillingAddress) + "#" + typeof(Address).ShortDisplayName(), subDependent1Entry.Metadata.Name); var subDependent2Entry = context.Entry(subDependent2); Assert.Equal(principal.Id, subDependent2Entry.Property("OrderDetailsId").CurrentValue); - Assert.Equal(nameof(OrderDetails.ShippingAddress), subDependent2Entry.Metadata.DefiningNavigationName); + Assert.Equal(typeof(OrderDetails).DisplayName() + "." + nameof(OrderDetails.ShippingAddress) + "#" + typeof(Address).ShortDisplayName(), subDependent2Entry.Metadata.Name); }); } diff --git a/test/EFCore.Tests/DbContextTest.cs b/test/EFCore.Tests/DbContextTest.cs index 02f8df37837..0fc7803accc 100644 --- a/test/EFCore.Tests/DbContextTest.cs +++ b/test/EFCore.Tests/DbContextTest.cs @@ -95,23 +95,6 @@ public void Local_does_not_call_DetectChanges_when_disabled() Assert.Equal(EntityState.Modified, entry.State); } - [ConditionalFact] - public void Set_throws_for_weak_types() - { - var model = new Model(); - var question = model.AddEntityType(typeof(Question), ConfigurationSource.Explicit); - model.AddEntityType(typeof(User), nameof(Question.Author), question, ConfigurationSource.Explicit); - - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder - .UseInMemoryDatabase(Guid.NewGuid().ToString()) - .UseInternalServiceProvider(InMemoryTestHelpers.Instance.CreateServiceProvider()) - .UseModel(model.FinalizeModel()); - using var context = new DbContext(optionsBuilder.Options); - var ex = Assert.Throws(() => context.Set().Local); - Assert.Equal(CoreStrings.InvalidSetTypeWeak(nameof(User)), ex.Message); - } - [ConditionalFact] public void Set_throws_for_shared_types() { diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 0813179c5bf..ddf7a0b8ad6 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -682,41 +682,6 @@ public virtual void Passes_on_valid_owned_entity_types() Validate(modelBuilder.Metadata); } - [ConditionalFact] - public virtual void Detects_weak_entity_type_without_defining_navigation() - { - var modelBuilder = CreateConventionlessInternalModelBuilder(); - var entityTypeBuilder = modelBuilder.Entity(typeof(SampleEntityMinimal), ConfigurationSource.Convention); - entityTypeBuilder.PrimaryKey(new[] { nameof(SampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var anotherEntityTypeBuilder = modelBuilder.Entity(typeof(AnotherSampleEntityMinimal), ConfigurationSource.Convention); - anotherEntityTypeBuilder.PrimaryKey(new[] { nameof(AnotherSampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var anotherOwnershipBuilder = anotherEntityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(AnotherSampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - anotherOwnershipBuilder.Metadata.DeclaringEntityType.Builder.PrimaryKey( - anotherOwnershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - - var ownershipBuilder = entityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(SampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - var ownedTypeBuilder = ownershipBuilder.Metadata.DeclaringEntityType.Builder; - ownedTypeBuilder.PrimaryKey(ownershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - - entityTypeBuilder.Metadata.RemoveNavigation(nameof(SampleEntityMinimal.ReferencedEntity)); - entityTypeBuilder.Ignore(nameof(SampleEntityMinimal.ReferencedEntity), ConfigurationSource.Explicit); - - VerifyError( - CoreStrings.NoDefiningNavigation( - nameof(SampleEntityMinimal.ReferencedEntity), - nameof(SampleEntityMinimal) - + "." - + nameof(SampleEntityMinimal.ReferencedEntity) - + "#" - + nameof(ReferencedEntityMinimal), - nameof(SampleEntityMinimal)), - modelBuilder.Metadata); - } - [ConditionalFact] public virtual void Detects_entity_type_with_multiple_ownerships() { @@ -746,79 +711,6 @@ public virtual void Detects_entity_type_with_multiple_ownerships() modelBuilder.Metadata); } - [ConditionalFact] - public virtual void Detects_weak_entity_type_with_non_defining_ownership() - { - var modelBuilder = CreateConventionlessInternalModelBuilder(); - var entityTypeBuilder = modelBuilder.Entity(typeof(SampleEntityMinimal), ConfigurationSource.Convention); - entityTypeBuilder.PrimaryKey(new[] { nameof(SampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var anotherEntityTypeBuilder = modelBuilder.Entity(typeof(AnotherSampleEntityMinimal), ConfigurationSource.Convention); - anotherEntityTypeBuilder.PrimaryKey(new[] { nameof(AnotherSampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var anotherOwnershipBuilder = anotherEntityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(AnotherSampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - anotherOwnershipBuilder.Metadata.DeclaringEntityType.Builder.PrimaryKey( - anotherOwnershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - - var ownershipBuilder = entityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(SampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - var ownedTypeBuilder = ownershipBuilder.Metadata.DeclaringEntityType.Builder; - ownedTypeBuilder.PrimaryKey(ownershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - - ownershipBuilder.Metadata.IsOwnership = false; - ownedTypeBuilder.HasRelationship( - entityTypeBuilder.Metadata, (string)null, null, ConfigurationSource.Convention, setTargetAsPrincipal: true) - .Metadata.IsOwnership = true; - - VerifyError( - CoreStrings.NonDefiningOwnership( - nameof(SampleEntityMinimal), - nameof(SampleEntityMinimal.ReferencedEntity), - nameof(SampleEntityMinimal) - + "." - + nameof(SampleEntityMinimal.ReferencedEntity) - + "#" - + nameof(ReferencedEntityMinimal)), - modelBuilder.Metadata); - } - - [ConditionalFact] - public virtual void Detects_weak_entity_type_without_ownership() - { - var modelBuilder = CreateConventionlessInternalModelBuilder(); - var entityTypeBuilder = modelBuilder.Entity(typeof(SampleEntityMinimal), ConfigurationSource.Convention); - entityTypeBuilder.PrimaryKey(new[] { nameof(SampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var ownershipBuilder = entityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(SampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - var ownedTypeBuilder = ownershipBuilder.Metadata.DeclaringEntityType.Builder; - ownedTypeBuilder.PrimaryKey(ownershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - - var anotherEntityTypeBuilder = modelBuilder.Entity(typeof(AnotherSampleEntityMinimal), ConfigurationSource.Convention); - anotherEntityTypeBuilder.PrimaryKey(new[] { nameof(AnotherSampleEntityMinimal.Id) }, ConfigurationSource.Convention); - - var anotherOwnershipBuilder = anotherEntityTypeBuilder.HasOwnership( - typeof(ReferencedEntityMinimal), nameof(AnotherSampleEntityMinimal.ReferencedEntity), ConfigurationSource.Convention); - anotherOwnershipBuilder.Metadata.DeclaringEntityType.Builder.PrimaryKey( - anotherOwnershipBuilder.Metadata.Properties.Select(p => p.Name).ToList(), ConfigurationSource.Convention); - anotherOwnershipBuilder.Metadata.IsOwnership = false; - - VerifyError( - CoreStrings.InconsistentOwnership( - nameof(SampleEntityMinimal) - + "." - + nameof(SampleEntityMinimal.ReferencedEntity) - + "#" - + nameof(ReferencedEntityMinimal), - nameof(AnotherSampleEntityMinimal) - + "." - + nameof(AnotherSampleEntityMinimal.ReferencedEntity) - + "#" - + nameof(ReferencedEntityMinimal)), - modelBuilder.Metadata); - } - [ConditionalFact] public virtual void Detects_principal_owned_entity_type() { diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index e61ac72914e..b6a27736ba2 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -257,9 +257,9 @@ public void OnEntityTypeAdded_calls_conventions_in_order(bool useBuilder, bool u { var conventions = new ConventionSet(); - var convention1 = new EntityTypeAddedConvention(terminate: false, onlyWeak: false); - var convention2 = new EntityTypeAddedConvention(terminate: true, onlyWeak: false); - var convention3 = new EntityTypeAddedConvention(terminate: false, onlyWeak: false); + var convention1 = new EntityTypeAddedConvention(terminate: false); + var convention2 = new EntityTypeAddedConvention(terminate: true); + var convention3 = new EntityTypeAddedConvention(terminate: false); conventions.EntityTypeAddedConventions.Add(convention1); conventions.EntityTypeAddedConventions.Add(convention2); conventions.EntityTypeAddedConventions.Add(convention3); @@ -296,68 +296,14 @@ public void OnEntityTypeAdded_calls_conventions_in_order(bool useBuilder, bool u Assert.Null(builder.Metadata.FindEntityType(typeof(Order))); } - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - [ConditionalTheory] - public void OnEntityTypeAdded_calls_conventions_in_order_for_weak_entity_types(bool useBuilder, bool useScope) - { - var conventions = new ConventionSet(); - - var convention1 = new EntityTypeAddedConvention(terminate: false, onlyWeak: true); - var convention2 = new EntityTypeAddedConvention(terminate: true, onlyWeak: true); - var convention3 = new EntityTypeAddedConvention(terminate: false, onlyWeak: true); - conventions.EntityTypeAddedConventions.Add(convention1); - conventions.EntityTypeAddedConventions.Add(convention2); - conventions.EntityTypeAddedConventions.Add(convention3); - - var builder = new InternalModelBuilder(new Model(conventions)); - var owner = builder.Entity(typeof(Order), ConfigurationSource.Explicit); - - var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; - - if (useBuilder) - { - // Add another owned type to trigger making them weak - owner.HasOwnership(typeof(OrderDetails), nameof(Order.OtherOrderDetails), ConfigurationSource.Convention); - var result = owner.HasOwnership(typeof(OrderDetails), nameof(Order.OrderDetails), ConfigurationSource.Convention); - - Assert.Equal(!useScope, result == null); - } - else - { - var result = builder.Metadata.AddEntityType( - typeof(OrderDetails), nameof(Order.OrderDetails), owner.Metadata, ConfigurationSource.Convention); - - Assert.Equal(!useScope, result == null); - } - - if (useScope) - { - Assert.Equal(0, convention1.Calls); - Assert.Equal(0, convention2.Calls); - scope.Dispose(); - } - - Assert.Equal(useBuilder ? 2 : 1, convention1.Calls); - Assert.Equal(useBuilder ? 2 : 1, convention2.Calls); - Assert.Equal(0, convention3.Calls); - - Assert.Empty(builder.Metadata.GetEntityTypes().Where(e => e.HasDefiningNavigation())); - Assert.Null(builder.Metadata.FindEntityType(typeof(OrderDetails))); - } - private class EntityTypeAddedConvention : IEntityTypeAddedConvention { private readonly bool _terminate; - private readonly bool _onlyWeak; public int Calls; - public EntityTypeAddedConvention(bool terminate, bool onlyWeak) + public EntityTypeAddedConvention(bool terminate) { _terminate = terminate; - _onlyWeak = onlyWeak; } public void ProcessEntityTypeAdded( @@ -365,29 +311,13 @@ public void ProcessEntityTypeAdded( IConventionContext context) { Assert.Same(entityTypeBuilder, entityTypeBuilder.Metadata.Builder); - if (entityTypeBuilder.Metadata.HasDefiningNavigation() == _onlyWeak) - { - Calls++; - } + + Calls++; if (_terminate) { - if (entityTypeBuilder.Metadata.HasDefiningNavigation()) - { - if (_onlyWeak) - { - entityTypeBuilder.ModelBuilder.HasNoEntityType(entityTypeBuilder.Metadata); - context.StopProcessing(); - } - } - else - { - if (!_onlyWeak) - { - entityTypeBuilder.Metadata.Model.RemoveEntityType(entityTypeBuilder.Metadata.Name); - context.StopProcessing(); - } - } + entityTypeBuilder.Metadata.Model.RemoveEntityType(entityTypeBuilder.Metadata.Name); + context.StopProcessing(); } } } diff --git a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs index e0e6fda7dfa..73a8ec9b7d1 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs @@ -928,34 +928,6 @@ public void Does_not_invert_if_both_entity_types_can_have_non_pk_fk_property() ValidateModel(); } - [ConditionalFact] - public void Does_not_invert_if_principal_entity_type_is_defining_the_weak_entity_type() - { - PrincipalType.Builder.Property(nameof(PrincipalEntity.DependentEntityKayPee), ConfigurationSource.Convention); - PrincipalType.Model.RemoveEntityType(typeof(DependentEntity)); - - var dependentType = PrincipalType.Model.AddEntityType( - typeof(DependentEntity), nameof(PrincipalEntity.InverseReferenceNav), PrincipalType, - ConfigurationSource.Convention); - var relationshipBuilder = dependentType.Builder.HasRelationship( - PrincipalType, null, nameof(PrincipalEntity.InverseReferenceNav), ConfigurationSource.Convention); - dependentType.Builder.PrimaryKey(new[] { nameof(DependentEntity.KayPee) }, ConfigurationSource.Convention); - - var newRelationshipBuilder = RunConvention(relationshipBuilder); - Assert.Same(relationshipBuilder, newRelationshipBuilder); - Assert.Same(dependentType, newRelationshipBuilder.Metadata.DeclaringEntityType); - - newRelationshipBuilder = RunConvention(newRelationshipBuilder); - - var fk = (IForeignKey)dependentType.GetForeignKeys().Single(); - Assert.Same(dependentType, fk.DeclaringEntityType); - Assert.Same(fk, newRelationshipBuilder.Metadata); - Assert.Same(PrimaryKey, fk.PrincipalKey.Properties.Single()); - Assert.True(fk.IsUnique); - - ValidateModel(); - } - [ConditionalFact] public void Does_not_invert_if_principal_entity_type_owns_the_weak_entity_type() { diff --git a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs index db579114979..b896f6a89da 100644 --- a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs @@ -348,45 +348,6 @@ public void InversePropertyAttribute_does_not_configure_ambiguous_navigations() nameof(AmbiguousPrincipal.Dependent)), logEntry.Message); } - [ConditionalFact] - public void InversePropertyAttribute_does_not_configure_non_defining_navigation() - { - var principalEntityTypeBuilder = CreateInternalEntityTypeBuilder(); - - var dependentEntityTypeBuilder = principalEntityTypeBuilder.ModelBuilder.Metadata.AddEntityType( - typeof(Dependent), nameof(Principal.Dependents), principalEntityTypeBuilder.Metadata, ConfigurationSource.Convention) - .Builder; - - dependentEntityTypeBuilder.HasRelationship( - principalEntityTypeBuilder.Metadata, - nameof(Dependent.Principal), - nameof(Principal.Dependents), - ConfigurationSource.Convention); - - Assert.Contains(principalEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Principal.Dependents)); - Assert.DoesNotContain(principalEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Principal.Dependent)); - Assert.Contains(dependentEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Dependent.Principal)); - - var convention = new InversePropertyAttributeConvention(CreateDependencies()); - convention.ProcessEntityTypeAdded( - dependentEntityTypeBuilder, - new ConventionContext( - dependentEntityTypeBuilder.Metadata.Model.ConventionDispatcher)); - - var logEntry = ListLoggerFactory.Log.Single(); - Assert.Equal(LogLevel.Warning, logEntry.Level); - Assert.Equal( - CoreResources.LogNonDefiningInverseNavigation(new TestLogger()).GenerateMessage( - nameof(Principal), nameof(Principal.Dependent), "Principal.Dependents#Dependent", nameof(Dependent.Principal), - nameof(Principal.Dependents)), logEntry.Message); - - Assert.Contains(principalEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Principal.Dependents)); - Assert.DoesNotContain(principalEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Principal.Dependent)); - Assert.Contains(dependentEntityTypeBuilder.Metadata.GetNavigations(), nav => nav.Name == nameof(Dependent.Principal)); - - Validate(dependentEntityTypeBuilder); - } - [ConditionalFact] public void InversePropertyAttribute_does_not_configure_non_ownership_navigation() { diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 12bdfd683bc..ec11734742c 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -137,9 +137,15 @@ public void Display_name_is_entity_type_name_when_no_CLR_type() "Everything.Is+Awesome>", CreateModel().AddEntityType("Everything.Is+Awesome>").DisplayName()); + [ConditionalFact] + public void Display_name_is_prettified_for_owned_shared_type() + => Assert.Equal( + "Is.We#re.Living#Our.Dream", + CreateModel().AddEntityType("Everything.Is.We#re.Living#Our.Dream", typeof(Dictionary)).DisplayName()); + [ConditionalFact] public void Display_name_is_entity_type_name_when_shared_entity_type() - => Assert.Equal("PostTag (Dictionary)", CreateModel().AddEntityType("PostTag", typeof(Dictionary)).DisplayName()); + => Assert.Equal("Everything.Is+PostTag (Dictionary)", CreateModel().AddEntityType("Everything.Is+PostTag", typeof(Dictionary)).DisplayName()); [ConditionalFact] public void Name_is_prettified_CLR_full_name() @@ -2581,45 +2587,6 @@ public void Indexes_are_ordered_by_property_count_then_property_names() Assert.True(new[] { i1, i2, i3, i4 }.SequenceEqual(customerType.GetIndexes())); } - [ConditionalFact] - public void Adding_inheritance_to_weak_entity_types_throws() - { - var model = CreateModel(); - var customerType = model.AddEntityType(typeof(Customer)); - var baseType = model.AddEntityType(typeof(BaseType), nameof(Customer.Orders), customerType); - var orderType = model.AddEntityType(typeof(Order), nameof(Customer.Orders), customerType); - var derivedType = model.AddEntityType(typeof(SpecialOrder), nameof(Customer.Orders), customerType); - - Assert.Equal( - CoreStrings.WeakDerivedType( - nameof(Customer) + "." + nameof(Customer.Orders) + "#" + nameof(Order)), - Assert.Throws(() => orderType.BaseType = baseType).Message); - Assert.Equal( - CoreStrings.WeakDerivedType( - nameof(Customer) + "." + nameof(Customer.Orders) + "#" + nameof(SpecialOrder)), - Assert.Throws(() => derivedType.BaseType = orderType).Message); - } - - [ConditionalFact] - public void Adding_non_delegated_inheritance_to_delegated_identity_definition_entity_types_throws() - { - var model = CreateModel(); - var customerType = model.AddEntityType(typeof(Customer)); - var baseType = model.AddEntityType(typeof(BaseType)); - var orderType = model.AddEntityType(typeof(Order), nameof(Customer.Orders), customerType); - var derivedType = model.AddEntityType(typeof(SpecialOrder)); - - Assert.Equal( - CoreStrings.WeakDerivedType( - nameof(Customer) + "." + nameof(Customer.Orders) + "#" + nameof(Order)), - Assert.Throws(() => orderType.BaseType = baseType).Message); - Assert.Equal( - CoreStrings.WeakBaseType( - typeof(SpecialOrder).DisplayName(fullName: false), - nameof(Customer) + "." + nameof(Customer.Orders) + "#" + nameof(Order)), - Assert.Throws(() => derivedType.BaseType = orderType).Message); - } - [ConditionalFact] public void Change_tracking_from_model_is_used_by_default_regardless_of_CLR_type() { @@ -2731,7 +2698,7 @@ public void Change_tracking_can_be_set_to_snapshot_only_for_non_notifying_entiti } [ConditionalFact] - public void Entity_type_with_deeply_nested_owned_weak_types_builds_correctly() + public void Entity_type_with_deeply_nested_owned_shared_types_builds_correctly() { using var context = new RejectionContext(nameof(RejectionContext)); var entityTypes = context.Model.GetEntityTypes(); @@ -2740,16 +2707,16 @@ public void Entity_type_with_deeply_nested_owned_weak_types_builds_correctly() new[] { "Application", - "ApplicationVersion", - "Rejection", "Application.Attitude#Attitude", - "ApplicationVersion.Attitude#Attitude", - "Rejection.FirstTest#FirstTest", "Application.Attitude#Attitude.FirstTest#FirstTest", - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", - "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff", "Application.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff" + "ApplicationVersion", + "ApplicationVersion.Attitude#Attitude", + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", + "Rejection", + "Rejection.FirstTest#FirstTest", + "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" }, entityTypes.Select(e => e.DisplayName()).ToList()); } @@ -2857,11 +2824,11 @@ List GetTypeNames() { "Application", "Attitude", + "Attitude.FirstTest#FirstTest", // FirstTest is shared + "Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", // SpecialistStaff is shared "Rejection", - "Attitude.FirstTest#FirstTest", // FirstTest is weak - "Rejection.FirstTest#FirstTest", // FirstTest is weak - "Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", // SpecialistStaff is weak - "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" // SpecialistStaff is weak + "Rejection.FirstTest#FirstTest", // FirstTest is shared + "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" // SpecialistStaff is shared }, GetTypeNames()); modelBuilder.Entity( @@ -2873,10 +2840,10 @@ List GetTypeNames() "Application", "ApplicationVersion", "Attitude", - "Rejection", "Attitude.FirstTest#FirstTest", - "Rejection.FirstTest#FirstTest", "Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", + "Rejection", + "Rejection.FirstTest#FirstTest", "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" }, GetTypeNames()); @@ -2884,28 +2851,28 @@ List GetTypeNames() x => x.Attitude, amb => { + amb.OwnsOne( + x => x.FirstTest, mb => + { + mb.OwnsOne(a => a.Tester); + }); + var typeNames = GetTypeNames(); Assert.Equal( new[] { "Application", + "Application.Attitude#Attitude", // Attitude becomes shared + "Application.Attitude#Attitude.FirstTest#FirstTest", // Attitude becomes shared + "Application.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", // Attitude becomes shared "ApplicationVersion", + "ApplicationVersion.Attitude#Attitude", // Attitude becomes shared + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", // Attitude becomes shared + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", // Attitude becomes shared "Rejection", - "Application.Attitude#Attitude", // Attitude becomes weak - "ApplicationVersion.Attitude#Attitude", // Attitude becomes weak "Rejection.FirstTest#FirstTest", - "Application.Attitude#Attitude.FirstTest#FirstTest", // Attitude becomes weak - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", // Attitude becomes weak - "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff", - "Application.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", // Attitude becomes weak - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff" // Attitude becomes weak + "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" }, typeNames); - - amb.OwnsOne( - x => x.FirstTest, mb => - { - mb.OwnsOne(a => a.Tester); - }); }); }); @@ -2913,16 +2880,16 @@ List GetTypeNames() new[] { "Application", - "ApplicationVersion", - "Rejection", "Application.Attitude#Attitude", - "ApplicationVersion.Attitude#Attitude", - "Rejection.FirstTest#FirstTest", "Application.Attitude#Attitude.FirstTest#FirstTest", - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", - "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff", "Application.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", - "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff" + "ApplicationVersion", + "ApplicationVersion.Attitude#Attitude", + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest", + "ApplicationVersion.Attitude#Attitude.FirstTest#FirstTest.Tester#SpecialistStaff", + "Rejection", + "Rejection.FirstTest#FirstTest", + "Rejection.FirstTest#FirstTest.Tester#SpecialistStaff" }, GetTypeNames()); } } diff --git a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs index 96e9299877e..9fc8f99a88b 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -329,19 +330,24 @@ public void Can_mark_type_as_owned_type() Assert.Null(modelBuilder.Ignore(typeof(Details), ConfigurationSource.Convention)); - Assert.Equal(2, model.GetEntityTypes(typeof(Details)).Count); + Assert.Equal(2, model.GetEntityTypes(typeof(Details)).Count()); Assert.Equal( - CoreStrings.ClashingOwnedEntityType(typeof(Details).Name), + CoreStrings.ClashingSharedType(typeof(Details).Name), Assert.Throws(() => modelBuilder.Entity(typeof(Details), ConfigurationSource.Explicit)).Message); + Assert.Equal( + CoreStrings.ClashingOwnedEntityType(typeof(Details).Name), + Assert.Throws(() + => modelBuilder.SharedTypeEntity(nameof(Details), typeof(Details), ConfigurationSource.Explicit)).Message); + Assert.NotNull(modelBuilder.Ignore(typeof(Details), ConfigurationSource.Explicit)); Assert.False(model.IsOwned(typeof(Details))); - Assert.NotNull(modelBuilder.Entity(typeof(Details), ConfigurationSource.Explicit)); + Assert.NotNull(modelBuilder.SharedTypeEntity(nameof(Details), typeof(Details), ConfigurationSource.Explicit)); - Assert.Empty(model.GetEntityTypes(typeof(Details)).Where(e => e.DefiningNavigationName != null)); + Assert.Empty(model.GetEntityTypes(typeof(Details)).Where(e => !e.HasSharedClrType)); Assert.Null(modelBuilder.Owned(typeof(Details), ConfigurationSource.Convention)); @@ -497,10 +503,9 @@ public void Can_add_shared_type() Assert.NotNull(modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit)); Assert.Equal( - CoreStrings.ClashingNonSharedType(typeof(Customer).DisplayName(), typeof(Customer).DisplayName()), + CoreStrings.ClashingNonSharedType(typeof(Customer).DisplayName(), typeof(Customer).ShortDisplayName()), Assert.Throws( - () => - modelBuilder.SharedTypeEntity(typeof(Customer).DisplayName(), typeof(Customer), ConfigurationSource.Explicit)) + () => modelBuilder.SharedTypeEntity(typeof(Customer).DisplayName(), typeof(Customer), ConfigurationSource.Explicit)) .Message); } diff --git a/test/EFCore.Tests/Metadata/Internal/ModelTest.cs b/test/EFCore.Tests/Metadata/Internal/ModelTest.cs index 4928a0fe6fc..401964a3047 100644 --- a/test/EFCore.Tests/Metadata/Internal/ModelTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ModelTest.cs @@ -143,61 +143,13 @@ public void Can_add_and_remove_shared_entity() } [ConditionalFact] - public void Can_add_weak_entity_types() + public void Adding_a_shared_entity_with_same_name_throws() { var model = CreateModel(); - var customerType = model.AddEntityType(typeof(Customer)); - var idProperty = customerType.AddProperty(Customer.IdProperty); - var customerKey = customerType.AddKey(idProperty); - var dependentOrderType = model.AddEntityType(typeof(Order), nameof(Customer.Orders), customerType); - - var fkProperty = dependentOrderType.AddProperty("ShadowId", typeof(int)); - var orderKey = dependentOrderType.AddKey(fkProperty); - var fk = dependentOrderType.AddForeignKey(fkProperty, customerKey, customerType); - var index = dependentOrderType.AddIndex(fkProperty); - - Assert.Same(fkProperty, dependentOrderType.GetProperties().Single()); - Assert.Same(orderKey, dependentOrderType.GetKeys().Single()); - Assert.Same(fk, dependentOrderType.GetForeignKeys().Single()); - Assert.Same(index, dependentOrderType.GetIndexes().Single()); - Assert.Equal(new[] { customerType, dependentOrderType }, model.GetEntityTypes()); - Assert.True(model.HasEntityTypeWithDefiningNavigation(typeof(Order))); - Assert.True(model.HasEntityTypeWithDefiningNavigation(typeof(Order).DisplayName())); - Assert.Same( - dependentOrderType, - model.FindEntityType(typeof(Order).DisplayName(), nameof(Customer.Orders), customerType)); - Assert.Same( - dependentOrderType, - model.FindEntityType(typeof(Order).DisplayName(), nameof(Customer.Orders), (IEntityType)customerType)); - Assert.Equal( - CoreStrings.ClashingWeakEntityType(typeof(Order).DisplayName(fullName: false)), - Assert.Throws(() => model.AddEntityType(typeof(Order))).Message); - Assert.Equal( - CoreStrings.ClashingNonWeakEntityType( - nameof(Customer) - + "." - + nameof(Customer.Orders) - + "#" - + nameof(Order) - + "." - + nameof(Order.Customer) - + "#" - + nameof(Customer)), - Assert.Throws( - () => model.AddEntityType(typeof(Customer), nameof(Order.Customer), dependentOrderType)).Message); - - Assert.Equal( - CoreStrings.ForeignKeySelfReferencingDependentEntityType( - nameof(Customer) + "." + nameof(Customer.Orders) + "#" + nameof(Order)), - Assert.Throws( - () => dependentOrderType.AddForeignKey(fkProperty, orderKey, dependentOrderType)).Message); - - Assert.Same( - dependentOrderType, model.RemoveEntityType( - typeof(Order), nameof(Customer.Orders), customerType)); - Assert.Null(((EntityType)dependentOrderType).Builder); - Assert.Empty(customerType.GetReferencingForeignKeys()); + Assert.Equal(CoreStrings.AmbiguousSharedTypeEntityTypeName(typeof(Customer).DisplayName()), + Assert.Throws(() + => model.AddEntityType(typeof(Customer).DisplayName(), typeof(Customer))).Message); } [ConditionalFact] diff --git a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs index 001b7f12976..b5abfeeb696 100644 --- a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs @@ -440,7 +440,7 @@ public virtual void Pulling_relationship_to_a_derived_type_with_fk_creates_relat Assert.Equal(nameof(Order.CustomerId), otherDerivedFk.Properties.Single().Name); } - [ConditionalFact] + [ConditionalFact(Skip = "Issue #18388")] public virtual void Can_promote_shadow_fk_to_the_base_type() { var modelBuilder = CreateModelBuilder(); diff --git a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs index 42d314b3e7d..3c249870b82 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; @@ -542,7 +543,7 @@ public virtual void Can_use_shared_Type_as_join_entity() Assert.Equal(typeof(Dictionary), shared2.ClrType); Assert.Equal( - CoreStrings.ClashingSharedType(typeof(Dictionary).DisplayName()), + CoreStrings.ClashingSharedType(typeof(Dictionary).ShortDisplayName()), Assert.Throws(() => modelBuilder.Entity>()).Message); modelBuilder.FinalizeModel(); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs index e87b1e69164..3555f204a27 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs @@ -38,7 +38,7 @@ public override void Reconfiguring_owned_type_as_non_owned_throws() modelBuilder.Entity().HasOne(c => c.Details)).Message); } - //Shadow navigations not supported #3864 + // Shadow navigations not supported #3864 public override void Can_configure_owned_type_collection_with_one_call() { } @@ -51,11 +51,6 @@ public override void OwnedType_can_derive_from_Collection() public override void Can_configure_owned_type_collection_with_one_call_afterwards() { } - - // Owned type's CLR type is not used - public override void Cannot_add_owned_type_with_same_clr_type_as_shared_type_entity_type() - { - } } public class NonGenericStringOneToManyType : OneToManyTestBase diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 485065f1522..55cab1bcbb3 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1692,7 +1692,7 @@ public virtual void Can_add_shared_type_entity_type() Assert.NotNull(shared2.FindProperty("Id")); Assert.Equal( - CoreStrings.ClashingSharedType(typeof(Dictionary).DisplayName()), + CoreStrings.ClashingSharedType(typeof(Dictionary).ShortDisplayName()), Assert.Throws(() => modelBuilder.Entity>()).Message); } @@ -1704,7 +1704,7 @@ public virtual void Cannot_add_shared_type_when_non_shared_exists() modelBuilder.Entity(); Assert.Equal( - CoreStrings.ClashingNonSharedType("Shared1", typeof(Customer).DisplayName()), + CoreStrings.ClashingNonSharedType("Shared1", nameof(Customer)), Assert.Throws(() => modelBuilder.SharedTypeEntity("Shared1")).Message); } } diff --git a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs index 7037505b673..0012239edc3 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs @@ -2602,7 +2602,7 @@ public virtual void Attempt_to_configure_Navigation_property_which_is_actually_a } [ConditionalFact] - public virtual void Navigation_to_shared_type_is_no_discovered_by_convention() + public virtual void Navigation_to_shared_type_is_not_discovered_by_convention() { var modelBuilder = CreateModelBuilder(); diff --git a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs index d9429d73577..79344e7cb49 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs @@ -4095,7 +4095,7 @@ public virtual void Navigation_properties_can_set_access_mode() } [ConditionalFact] - public virtual void Navigation_to_shared_type_is_no_discovered_by_convention() + public virtual void Navigation_to_shared_type_is_not_discovered_by_convention() { var modelBuilder = CreateModelBuilder(); diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index f8cdfbe95d9..e0f70cbdb64 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -504,7 +504,7 @@ public virtual void Can_configure_one_to_one_relationship_from_an_owned_type_col Assert.Null(foreignKey.PrincipalToDependent); Assert.NotEqual(ownership1.Properties.Single().Name, foreignKey.Properties.Single().Name); Assert.Equal(5, model.GetEntityTypes().Count()); - Assert.Equal(2, model.GetEntityTypes(typeof(Order)).Count); + Assert.Equal(2, model.GetEntityTypes(typeof(Order)).Count()); Assert.Equal(2, ownership1.DeclaringEntityType.GetForeignKeys().Count()); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(Order))); @@ -786,8 +786,8 @@ public virtual void Can_configure_owned_type_collection_with_one_call() nameof(SpecialOrder.SpecialOrderId), specialOwnership.DeclaringEntityType.FindPrimaryKey().Properties.Single().Name); Assert.Equal(9, modelBuilder.Model.GetEntityTypes().Count()); - Assert.Equal(2, modelBuilder.Model.GetEntityTypes(typeof(Order)).Count); - Assert.Equal(7, modelBuilder.Model.GetEntityTypes().Count(e => !e.HasDefiningNavigation())); + Assert.Equal(2, modelBuilder.Model.GetEntityTypes(typeof(Order)).Count()); + Assert.Equal(7, modelBuilder.Model.GetEntityTypes().Count(e => !e.HasSharedClrType)); Assert.Equal(5, modelBuilder.Model.GetEntityTypes().Count(e => e.IsOwned())); var conventionModel = (IConventionModel)modelBuilder.Model; @@ -834,8 +834,9 @@ public virtual void Can_configure_owned_type_collection_with_one_call_afterwards nameof(SpecialOrder.SpecialOrderId), specialOwnership.DeclaringEntityType.FindPrimaryKey().Properties.Single().Name); Assert.Equal(9, modelBuilder.Model.GetEntityTypes().Count()); - Assert.Equal(2, modelBuilder.Model.GetEntityTypes(typeof(Order)).Count); - Assert.Equal(7, modelBuilder.Model.GetEntityTypes().Count(e => !e.HasDefiningNavigation())); + Assert.Equal(2, modelBuilder.Model.GetEntityTypes(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; @@ -1286,7 +1287,7 @@ public virtual void Can_configure_self_ownership() .ForeignKey; var selfOwnership = bookLabelOwnership.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; Assert.NotSame(selfOwnership.PrincipalEntityType, selfOwnership.DeclaringEntityType); - Assert.Equal(selfOwnership.PrincipalEntityType.Name, selfOwnership.DeclaringEntityType.Name); + Assert.Equal(selfOwnership.PrincipalEntityType.ClrType, selfOwnership.DeclaringEntityType.ClrType); Assert.True(selfOwnership.IsOwnership); Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); @@ -1356,8 +1357,7 @@ public virtual void Configuring_base_type_as_owned_throws() Assert.Equal( CoreStrings.ClashingNonOwnedDerivedEntityType(nameof(BookLabel), nameof(AnotherBookLabel)), Assert.Throws( - () => - modelBuilder.Entity().OwnsOne(c => c.Label)).Message); + () => modelBuilder.Entity().OwnsOne(c => c.Label)).Message); } [ConditionalFact] @@ -1595,36 +1595,6 @@ public virtual void Shared_type_used_as_owned_type_throws_for_same_name() b.OwnsMany("Shared1", e => e.Collection)).Message); }); } - - [ConditionalFact] - public virtual void Cannot_add_shared_type_with_same_clr_type_as_weak_entity_type() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity( - b => - { - b.OwnsOne(e => e.Bill1); - b.OwnsOne(e => e.Bill2); - }); - - Assert.Equal( - CoreStrings.ClashingNonSharedType("Shared1", typeof(BillingDetail).DisplayName()), - Assert.Throws( - () => modelBuilder.SharedTypeEntity("Shared1")).Message); - } - - [ConditionalFact] - public virtual void Cannot_add_owned_type_with_same_clr_type_as_shared_type_entity_type() - { - var modelBuilder = CreateModelBuilder(); - var entityTypeBuilder = modelBuilder.Entity(); - - Assert.Equal( - CoreStrings.ClashingSharedType(typeof(Dictionary).DisplayName()), - Assert.Throws( - () => entityTypeBuilder.OwnsOne(e => e.Reference)).Message); - } } } } diff --git a/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs index 19cdec10423..1f07402c917 100644 --- a/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ShadowEntityTypeTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding public class ShadowEntityTypeTest { [ConditionalFact] - public virtual void Can_create_two_shadow_weak_owned_types() + public virtual void Can_create_two_shadow_owned_types() { var modelBuilder = CreateModelBuilder(); @@ -62,11 +62,11 @@ public virtual void Can_create_two_shadow_weak_owned_types() Assert.Equal( ownership1.DeclaringEntityType.FindPrimaryKey().Properties.Single().Name, ownership2.DeclaringEntityType.FindPrimaryKey().Properties.Single().Name); - Assert.Equal(2, model.GetEntityTypes().Count(e => e.Name == "CustomerDetails")); + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ShortName() == "CustomerDetails")); } [ConditionalFact] - public virtual void Can_create_One_to_One_shadow_navigations_between_shadow_entity_types() + public virtual void Can_create_one_to_one_shadow_navigations_between_shadow_entity_types() { var modelBuilder = CreateModelBuilder(); var foreignKey = modelBuilder.Entity("Order") @@ -85,7 +85,7 @@ public virtual void Can_create_One_to_One_shadow_navigations_between_shadow_enti } [ConditionalFact] - public virtual void Can_create_One_to_Many_shadow_navigations_between_shadow_entity_types() + public virtual void Can_create_one_to_many_shadow_navigations_between_shadow_entity_types() { var modelBuilder = CreateModelBuilder(); var foreignKey = modelBuilder.Entity("Order")