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")