From a926a92a784ff5b2787bccbb9cc04b46bd4620be Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 19 Aug 2020 11:25:27 -0700 Subject: [PATCH] Add FK constraints between TPT tables Only added if there isn't an equivalent FK already in the model The new FKs are used to sort the TPT update commands instead of the ad-hoc solution Fix recursive issues exposed by the new FKs Fixes #21943 --- .../EntityTypeHierarchyMappingConvention.cs | 10 ++ .../Conventions/SharedTableConvention.cs | 14 +- .../RelationalForeignKeyExtensions.cs | 8 +- .../Metadata/Internal/RelationalModel.cs | 14 +- .../Query/SqlExpressionFactory.cs | 8 +- .../Update/Internal/CommandBatchPreparer.cs | 33 +--- .../Update/ModificationCommand.cs | 5 - .../Extensions/SqlServerPropertyExtensions.cs | 3 +- .../SqlServerOnDeleteConvention.cs | 5 + .../ChangeTracking/Internal/StateManager.cs | 5 + src/EFCore/Extensions/ForeignKeyExtensions.cs | 13 ++ src/EFCore/Infrastructure/ModelValidator.cs | 7 +- .../ForeignKeyPropertyDiscoveryConvention.cs | 4 +- .../Conventions/ValueGenerationConvention.cs | 13 +- .../Metadata/Internal/ForeignKeyExtensions.cs | 9 -- .../Internal/InternalEntityTypeBuilder.cs | 8 +- .../Internal/InternalForeignKeyBuilder.cs | 1 + .../Migrations/ModelSnapshotSqlServerTest.cs | 18 +++ .../TPTTableSplittingTestBase.cs | 33 +++- .../TableSplittingTestBase.cs | 3 +- .../Metadata/RelationalModelTest.cs | 11 +- .../Internal/MigrationsModelDifferTest.cs | 142 +++++++++++++----- .../InheritanceRelationshipsQueryTestBase.cs | 2 +- .../Query/OwnedQuerySqlServerTest.cs | 122 +++------------ .../Query/TPTInheritanceQuerySqlServerTest.cs | 2 - .../TPTTableSplittingSqlServerTest.cs | 14 +- .../Migrations/SqlServerModelDifferTest.cs | 21 +++ .../Infrastructure/ModelValidatorTest.cs | 9 +- .../ValueGeneratorConventionTest.cs | 12 +- .../Metadata/Internal/ForeignKeyTest.cs | 27 ++-- 30 files changed, 333 insertions(+), 243 deletions(-) diff --git a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs index 76c191d9a47..9b870b32a38 100644 --- a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; @@ -42,6 +43,15 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, && (tableName != entityType.BaseType.GetTableName() || schema != entityType.BaseType.GetSchema())) { + var pk = entityType.FindPrimaryKey(); + if (pk != null + && !entityType.FindDeclaredForeignKeys(pk.Properties) + .Any(fk => fk.PrincipalKey.IsPrimaryKey() && fk.PrincipalEntityType.IsAssignableFrom(entityType))) + { + entityType.Builder.HasRelationship(entityType.BaseType, pk.Properties, entityType.BaseType.FindPrimaryKey()) + .IsUnique(true); + } + nonTphRoots.Add(entityType.GetRootType()); } diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs index a90311435c4..9daa096b0fe 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs @@ -373,8 +373,14 @@ private void TryUniquifyForeignKeyNames( in StoreObjectIdentifier storeObject, int maxLength) { - foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) + foreach (var foreignKey in entityType.GetForeignKeys()) { + if (foreignKey.DeclaringEntityType != entityType + && StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, StoreObjectType.Table) == storeObject) + { + continue; + } + var principalTable = foreignKey.PrincipalKey.IsPrimaryKey() ? StoreObjectIdentifier.Create(foreignKey.PrincipalEntityType, StoreObjectType.Table) : StoreObjectIdentifier.Create(foreignKey.PrincipalKey.DeclaringEntityType, StoreObjectType.Table); @@ -408,6 +414,12 @@ private void TryUniquifyForeignKeyNames( continue; } + if (!otherForeignKey.DeclaringEntityType.IsAssignableFrom(entityType) + && !entityType.IsAssignableFrom(otherForeignKey.DeclaringEntityType)) + { + continue; + } + var newOtherForeignKeyName = TryUniquify(otherForeignKey, foreignKeyName, foreignKeys, maxLength); if (newOtherForeignKeyName != null) { diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs index 63bc9b2fc39..b5e55e7ba5d 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs @@ -139,7 +139,9 @@ public static bool AreCompatible( return false; } - if (foreignKey.DeleteBehavior != duplicateForeignKey.DeleteBehavior) + var referentialAction = RelationalModel.ToReferentialAction(foreignKey.DeleteBehavior); + var duplicateReferentialAction = RelationalModel.ToReferentialAction(duplicateForeignKey.DeleteBehavior); + if (referentialAction != duplicateReferentialAction) { if (shouldThrow) { @@ -151,8 +153,8 @@ public static bool AreCompatible( duplicateForeignKey.DeclaringEntityType.DisplayName(), foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(), foreignKey.GetConstraintName(storeObject, principalTable.Value), - foreignKey.DeleteBehavior, - duplicateForeignKey.DeleteBehavior)); + referentialAction, + duplicateReferentialAction)); } return false; diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index c3deafae37d..ac81bb44798 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -750,6 +750,12 @@ private static void PopulateConstraints(Table table) var storeObject = StoreObjectIdentifier.Table(table.Name, table.Schema); foreach (var entityTypeMapping in ((ITable)table).EntityTypeMappings) { + if (!entityTypeMapping.IncludesDerivedTypes + && entityTypeMapping.EntityType.GetTableMappings().Any(m => m.IncludesDerivedTypes)) + { + continue; + } + var entityType = (IConventionEntityType)entityTypeMapping.EntityType; foreach (var foreignKey in entityType.GetForeignKeys()) { @@ -1061,7 +1067,13 @@ private static void PopulateRowInternalForeignKeys(TableBase table) } } - private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior) + /// + /// 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 ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior) { switch (deleteBehavior) { diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index f3fbb45bacc..16ca637a883 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -948,9 +948,15 @@ private void AddOptionalDependentConditions( // other dependents. foreach (var referencingFk in entityType.GetReferencingForeignKeys()) { + if (referencingFk.PrincipalEntityType.IsAssignableFrom(entityType)) + { + continue; + } + var otherSelectExpression = new SelectExpression(entityType, this); - var sameTable = table.IsOptional(referencingFk.DeclaringEntityType); + var sameTable = table.EntityTypeMappings.Any(m => m.EntityType == referencingFk.DeclaringEntityType) + && table.IsOptional(referencingFk.DeclaringEntityType); AddInnerJoin( otherSelectExpression, referencingFk, sameTable ? table : null); diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 8ce48b76155..8a3c5abcbf2 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -177,7 +177,6 @@ protected virtual IEnumerable CreateModificationCommands( var mappings = (IReadOnlyCollection)entry.EntityType.GetTableMappings(); var mappingCount = mappings.Count; ModificationCommand firstCommand = null; - var relatedCommands = mappingCount > 1 ? new List(mappingCount) : null; foreach (var mapping in mappings) { var table = mapping.Table; @@ -216,33 +215,12 @@ protected virtual IEnumerable CreateModificationCommands( Check.DebugAssert(firstCommand == null, "firstCommand == null"); firstCommand = command; } - - if (relatedCommands != null) - { - relatedCommands.Add(command); - } } if (firstCommand == null) { throw new InvalidOperationException(RelationalStrings.ReadonlyEntitySaved(entry.EntityType.DisplayName())); } - - if (relatedCommands != null) - { - if (firstCommand.EntityState == EntityState.Deleted) - { - relatedCommands.Reverse(); - } - - var previousCommand = relatedCommands[0]; - for (var i = 1; i < relatedCommands.Count; i++) - { - var relatedCommand = relatedCommands[i]; - relatedCommand.Predecessor = previousCommand; - previousCommand = relatedCommand; - } - } } if (sharedTablesCommandsMap != null) @@ -569,12 +547,6 @@ private void AddForeignKeyEdges( { foreach (var command in commandGraph.Vertices) { - if (command.Predecessor != null) - { - // This is usually an implicit relationship between TPT rows for the same entity - commandGraph.AddEdge(command.Predecessor, command, null); - } - switch (command.EntityState) { case EntityState.Modified: @@ -585,9 +557,8 @@ private void AddForeignKeyEdges( var entry = command.Entries[entryIndex]; foreach (var foreignKey in entry.EntityType.GetForeignKeys()) { - var constraints = foreignKey.GetMappedConstraints(); - - if (!constraints.Any() + if (!foreignKey.GetMappedConstraints() + .Any(c => c.Table.Name == command.TableName && c.Table.Schema == command.Schema) || (command.EntityState == EntityState.Modified && !foreignKey.Properties.Any(p => entry.IsModified(p)))) { diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 1275477c6c7..4d852d054dc 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -90,11 +90,6 @@ public ModificationCommand( /// public virtual string Schema { get; } - /// - /// The command that needs to be executed before this one. - /// - public virtual ModificationCommand Predecessor { get; [param: CanBeNull] set; } - /// /// The s that represent the entities that are mapped to the row /// to update. diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index e0f91a42f11..14ef5a2bdac 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -335,7 +336,7 @@ public static SqlServerValueGenerationStrategy GetValueGenerationStrategy( } if (property.ValueGenerated != ValueGenerated.OnAdd - || property.IsForeignKey() + || property.GetContainingForeignKeys().Any(fk => !fk.IsBaseLinking()) || property.GetDefaultValue(storeObject) != null || property.GetDefaultValueSql(storeObject) != null || property.GetComputedColumnSql(storeObject) != null) diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs index a1bf026392a..ad5b16bf42d 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs @@ -46,6 +46,11 @@ protected override DeleteBehavior GetTargetDeleteBehavior(IConventionForeignKey return deleteBehavior; } + if (foreignKey.IsBaseLinking()) + { + return DeleteBehavior.ClientCascade; + } + var selfReferencingSkipNavigation = foreignKey.GetReferencingSkipNavigations() .FirstOrDefault(s => s.Inverse != null && s.TargetEntityType == s.DeclaringEntityType); if (selfReferencingSkipNavigation == null) diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 1de65f14d08..6f2d2ec4a1f 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -996,6 +996,11 @@ public virtual void CascadeDelete(InternalEntityEntry entry, bool force, IEnumer foreach (InternalEntityEntry dependent in (GetDependentsFromNavigation(entry, fk) ?? GetDependents(entry, fk)).ToList()) { + if (dependent.SharedIdentityEntry == entry) + { + continue; + } + changeDetector?.DetectChanges(dependent); if (dependent.EntityState != EntityState.Deleted diff --git a/src/EFCore/Extensions/ForeignKeyExtensions.cs b/src/EFCore/Extensions/ForeignKeyExtensions.cs index 1f20728ecc1..55d20168044 100644 --- a/src/EFCore/Extensions/ForeignKeyExtensions.cs +++ b/src/EFCore/Extensions/ForeignKeyExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Text; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -71,6 +72,18 @@ public static IEntityType GetRelatedEntityType([NotNull] this IForeignKey foreig public static INavigation GetNavigation([NotNull] this IForeignKey foreignKey, bool pointsToPrincipal) => pointsToPrincipal ? foreignKey.DependentToPrincipal : foreignKey.PrincipalToDependent; + /// + /// Returns a value indicating whether the foreign key is defined on the primary key and pointing to the same primary key. + /// + /// The foreign key. + /// A value indicating whether the foreign key is defined on the primary key and pointing to the same primary key. + public static bool IsBaseLinking([NotNull] this IForeignKey foreignKey) + { + var primaryKey = foreignKey.DeclaringEntityType.FindPrimaryKey(); + return primaryKey == foreignKey.PrincipalKey + && foreignKey.Properties.SequenceEqual(primaryKey.Properties); + } + /// /// /// Creates a human-readable representation of the given metadata. diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index c44f1b761e0..415669c088a 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -477,7 +477,7 @@ protected virtual void ValidateNoCycles( var principalType = foreignKey.PrincipalEntityType; if (!foreignKey.PrincipalKey.IsPrimaryKey() || !unvalidatedEntityTypes.Contains(principalType) - || foreignKey.PrincipalEntityType == entityType + || foreignKey.PrincipalEntityType.IsAssignableFrom(entityType) || !PropertyListComparer.Instance.Equals(foreignKey.Properties, primaryKey.Properties)) { continue; @@ -770,12 +770,13 @@ protected virtual void ValidateForeignKeys( foreach (var declaredForeignKey in entityType.GetDeclaredForeignKeys()) { if (declaredForeignKey.PrincipalEntityType == declaredForeignKey.DeclaringEntityType - && PropertyListComparer.Instance.Equals(declaredForeignKey.PrincipalKey.Properties, declaredForeignKey.Properties)) + && declaredForeignKey.PrincipalKey.Properties.SequenceEqual(declaredForeignKey.Properties)) { logger.RedundantForeignKeyWarning(declaredForeignKey); } - if (entityType.BaseType == null) + if (entityType.BaseType == null + || declaredForeignKey.IsBaseLinking()) { continue; } diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs index cad68e9eda2..65437509dda 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs @@ -157,7 +157,9 @@ private IConventionForeignKeyBuilder DiscoverProperties( || foreignKey.DeclaringEntityType.IsKeyless || (!foreignKey.IsUnique && !ConfigurationSource.Convention.Overrides(foreignKey.GetIsUniqueConfigurationSource())) || foreignKey.PrincipalToDependent?.IsCollection == true - || foreignKey.DeclaringEntityType.FindOwnership() != null) + || foreignKey.DeclaringEntityType.FindOwnership() != null + || (foreignKey.IsBaseLinking() + && foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType))) { relationshipBuilder = relationshipBuilder.HasEntityTypes( foreignKey.PrincipalEntityType, foreignKey.DeclaringEntityType); diff --git a/src/EFCore/Metadata/Conventions/ValueGenerationConvention.cs b/src/EFCore/Metadata/Conventions/ValueGenerationConvention.cs index f24a0977ead..d6225e10c40 100644 --- a/src/EFCore/Metadata/Conventions/ValueGenerationConvention.cs +++ b/src/EFCore/Metadata/Conventions/ValueGenerationConvention.cs @@ -44,9 +44,13 @@ public ValueGenerationConvention([NotNull] ProviderConventionSetBuilderDependenc public virtual void ProcessForeignKeyAdded( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) { - foreach (var property in relationshipBuilder.Metadata.Properties) + var foreignKey = relationshipBuilder.Metadata; + if (!foreignKey.IsBaseLinking()) { - property.Builder.ValueGenerated(ValueGenerated.Never); + foreach (var property in foreignKey.Properties) + { + property.Builder.ValueGenerated(ValueGenerated.Never); + } } } @@ -82,7 +86,8 @@ public virtual void ProcessForeignKeyPropertiesChanged( { OnForeignKeyRemoved(oldDependentProperties); - if (relationshipBuilder.Metadata.Builder != null) + if (relationshipBuilder.Metadata.Builder != null + && !foreignKey.IsBaseLinking()) { foreach (var property in foreignKey.Properties) { @@ -179,7 +184,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( /// The property. /// The store value generation strategy to set for the given property. public static ValueGenerated? GetValueGenerated([NotNull] IProperty property) - => !property.IsForeignKey() + => !property.GetContainingForeignKeys().Any(fk => !fk.IsBaseLinking()) && ShouldHaveGeneratedProperty(property.FindContainingPrimaryKey()) && CanBeGenerated(property) ? ValueGenerated.OnAdd diff --git a/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs b/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs index ba5878e1273..c5f29421dda 100644 --- a/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs +++ b/src/EFCore/Metadata/Internal/ForeignKeyExtensions.cs @@ -27,15 +27,6 @@ public static class ForeignKeyExtensions public static bool IsSelfReferencing([NotNull] this IForeignKey foreignKey) => foreignKey.DeclaringEntityType == foreignKey.PrincipalEntityType; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// 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 IsSelfPrimaryKeyReferencing([NotNull] this IForeignKey foreignKey) - => foreignKey.DeclaringEntityType.FindPrimaryKey() == foreignKey.PrincipalKey; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 6970c00af11..b1f0c819313 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -2796,7 +2796,7 @@ private InternalForeignKeyBuilder HasRelationship( principalKey: null, propertyBaseName: navigationProperty?.GetSimpleMemberName(), required: required, - configurationSource: configurationSource); + configurationSource); } else { @@ -2812,7 +2812,7 @@ private InternalForeignKeyBuilder HasRelationship( principalKey: null, propertyBaseName: navigationProperty?.GetSimpleMemberName(), required: null, - configurationSource: configurationSource); + configurationSource); } relationship = newRelationship; @@ -3884,8 +3884,8 @@ private IReadOnlyList CreateUniqueProperties( using var principalPropertyTypesEnumerator = principalPropertyTypes.GetEnumerator(); for (var i = 0; i < propertyCount - && principalPropertyNamesEnumerator.MoveNext() - && principalPropertyTypesEnumerator.MoveNext(); + && principalPropertyNamesEnumerator.MoveNext() + && principalPropertyTypesEnumerator.MoveNext(); i++) { var keyPropertyName = principalPropertyNamesEnumerator.Current; diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index f20f358e7f0..d36f5065a4c 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -1894,6 +1894,7 @@ private bool CanSetForeignKey( // FKs are not allowed to use properties from inherited keys since this could result in an ambiguous value space if (dependentEntityType.BaseType != null + && !principalEntityType.IsAssignableFrom(dependentEntityType) && configurationSource != ConfigurationSource.Explicit // let it throw for explicit && properties.Any(p => p.GetContainingKeys().Any(k => k.DeclaringEntityType != dependentEntityType))) { diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index ec2f8c2022d..005bf8fdbc5 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -416,6 +416,15 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() .HasColumnType(""nvarchar(max)""); b.ToTable(""DerivedEntity"", ""foo""); + }); + + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b => + { + b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", null) + .WithOne() + .HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", ""Id"") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); });"), o => { @@ -462,6 +471,15 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu .HasColumnType(""nvarchar(max)""); b.ToTable(""DerivedEntity"", ""foo"", t => t.ExcludeFromMigrations()); + }); + + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b => + { + b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", null) + .WithOne() + .HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", ""Id"") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); });"), o => { diff --git a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs index ccaadd37d44..37eb79a5e4f 100644 --- a/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TPTTableSplittingTestBase.cs @@ -26,20 +26,45 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("Vehicles"); modelBuilder.Entity().ToTable("PoweredVehicles"); - modelBuilder.Entity().ToTable("Vehicles"); + modelBuilder.Entity( + eb => + { + eb.ToTable("Vehicles"); + eb.HasOne(e => e.Vehicle) + .WithOne(e => e.Operator) + .HasForeignKey(e => e.VehicleName) + .OnDelete(DeleteBehavior.ClientCascade); + eb.HasOne(e => e.Details) + .WithOne() + .HasForeignKey(e => e.VehicleName) + .OnDelete(DeleteBehavior.ClientCascade); + }); + modelBuilder.Entity().ToTable("LicensedOperators"); modelBuilder.Entity().ToTable("Vehicles"); modelBuilder.Entity().ToTable("PoweredVehicles") - .HasOne(e => e.Vehicle).WithOne(e => e.Engine).OnDelete(DeleteBehavior.NoAction); + .HasOne(e => e.Vehicle).WithOne(e => e.Engine).OnDelete(DeleteBehavior.ClientCascade); modelBuilder.Entity().ToTable("CombustionEngines"); modelBuilder.Entity().ToTable("IntermittentCombustionEngines"); modelBuilder.Entity().ToTable("ContinuousCombustionEngines"); modelBuilder.Entity().ToTable("SolidRockets").Ignore(e => e.SolidFuelTank); - modelBuilder.Entity().ToTable("CombustionEngines") - .HasOne(e => e.Vehicle).WithOne().OnDelete(DeleteBehavior.NoAction); + modelBuilder.Entity( + eb => + { + eb.ToTable("CombustionEngines"); + + eb.HasOne(e => e.Engine) + .WithOne(e => e.FuelTank) + .HasForeignKey(e => e.VehicleName) + .OnDelete(DeleteBehavior.ClientCascade); + eb.HasOne(e => e.Vehicle) + .WithOne() + .HasForeignKey(e => e.VehicleName) + .OnDelete(DeleteBehavior.ClientCascade); + }); modelBuilder.Entity().ToTable("SolidFuelTanks").Ignore(e => e.Rocket); } } diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs index 314b988e82f..fc23608057f 100644 --- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs @@ -346,7 +346,8 @@ public virtual void Can_manipulate_entities_sharing_row_independently() using (var context = CreateContext()) { - var streetcarFromStore = context.Set().Include(v => v.Engine).AsNoTracking() + var streetcarFromStore = context.Set().AsNoTracking() + .Include(v => v.Engine).Include(v => v.Operator) .Single(v => v.Name == "1984 California Car"); Assert.Null(streetcarFromStore.Engine); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index b63040de965..17d00cb0351 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -435,12 +435,19 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Equal("AK_Customer_SpecialityAk", specialCustomerUniqueConstraint.Name); Assert.NotNull(specialCustomerUniqueConstraint.MappedKeys.Single()); - var specialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.First(); + var foreignKeys = specialCustomerTable.ForeignKeyConstraints.ToArray(); + + var specialCustomerTptFkConstraint = foreignKeys[0]; + Assert.Equal("FK_SpecialCustomer_Customer_Id", specialCustomerTptFkConstraint.Name); + Assert.NotNull(specialCustomerTptFkConstraint.MappedForeignKeys.Single()); + Assert.Same(customerTable, specialCustomerTptFkConstraint.PrincipalTable); + + var specialCustomerFkConstraint = foreignKeys[1]; Assert.Equal("FK_SpecialCustomer_Customer_RelatedCustomerSpeciality", specialCustomerFkConstraint.Name); Assert.NotNull(specialCustomerFkConstraint.MappedForeignKeys.Single()); Assert.Same(customerTable, specialCustomerFkConstraint.PrincipalTable); - var anotherSpecialCustomerFkConstraint = specialCustomerTable.ForeignKeyConstraints.Last(); + var anotherSpecialCustomerFkConstraint = foreignKeys[2]; Assert.Equal("FK_SpecialCustomer_SpecialCustomer_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name); Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single()); Assert.Same(specialCustomerTable, anotherSpecialCustomerFkConstraint.PrincipalTable); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 545a6888617..6537804e70a 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -5316,6 +5316,8 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() "Dog", x => { x.ToTable("Dogs"); + x.HasOne("Animal", null).WithOne().HasForeignKey("Dog", "Id") + .HasConstraintName("FK_Dogs_Animal"); x.HasData( new { Id = 22, PreyId = 33 }, new { Id = 23 }); @@ -5419,13 +5421,24 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Cats", pk.Table); Assert.Equal(new[] { "Id" }, pk.Columns); - var fk = operation.ForeignKeys.Single(); - Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); - Assert.Equal("Cats", fk.Table); - Assert.Equal("Animal", fk.PrincipalTable); - Assert.Equal(new[] { "PreyId" }, fk.Columns); - Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); - Assert.Equal(ReferentialAction.Restrict, fk.OnDelete); + Assert.Collection(operation.ForeignKeys, + fk => { + Assert.Equal("FK_Cats_Animal_Id", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + }, + fk => + { + Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Restrict, fk.OnDelete); + }); Assert.Empty(operation.UniqueConstraints); Assert.Null(operation.Comment); @@ -5461,7 +5474,14 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Mice", pk.Table); Assert.Equal(new[] { "Id" }, pk.Columns); - Assert.Empty(operation.ForeignKeys); + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Animal_Id", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + Assert.Empty(operation.UniqueConstraints); Assert.Null(operation.Comment); Assert.Empty(operation.CheckConstraints); @@ -5534,31 +5554,31 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(23, v), - v => Assert.Null(v)); + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Mice", operation.Table); - Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(33, v)); + v => Assert.Equal(23, v), + v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(22, v), v => Assert.Equal(33, v)); }, o => @@ -5586,6 +5606,16 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal(ReferentialAction.Restrict, operation.OnDelete); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); @@ -5604,6 +5634,12 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data() Assert.Equal("Animal", operation.Table); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); @@ -6017,13 +6053,24 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Cats", pk.Table); Assert.Equal(new[] { "Id" }, pk.Columns); - var fk = operation.ForeignKeys.Single(); - Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); - Assert.Equal("Cats", fk.Table); - Assert.Equal("Animal", fk.PrincipalTable); - Assert.Equal(new[] { "PreyId" }, fk.Columns); - Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); - Assert.Equal(ReferentialAction.Restrict, fk.OnDelete); + Assert.Collection(operation.ForeignKeys, + fk => { + Assert.Equal("FK_Cats_Animal_Id", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + }, + fk => + { + Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Restrict, fk.OnDelete); + }); Assert.Empty(operation.UniqueConstraints); Assert.Null(operation.Comment); @@ -6059,7 +6106,14 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Mice", pk.Table); Assert.Equal(new[] { "Id" }, pk.Columns); - Assert.Empty(operation.ForeignKeys); + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Animal_Id", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + Assert.Empty(operation.UniqueConstraints); Assert.Null(operation.Comment); Assert.Empty(operation.CheckConstraints); @@ -6132,31 +6186,31 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() { var operation = Assert.IsType(o); Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(23, v), - v => Assert.Null(v)); + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Mice", operation.Table); - Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(33, v)); + v => Assert.Equal(23, v), + v => Assert.Null(v)); }, o => { var operation = Assert.IsType(o); - Assert.Equal("Dogs", operation.Table); - - Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); - Assert.Null(operation.ColumnTypes); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); AssertMultidimensionalArray( operation.Values, - v => Assert.Equal(22, v), v => Assert.Equal(33, v)); }, o => @@ -6184,6 +6238,16 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal(ReferentialAction.Restrict, operation.OnDelete); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_Id", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); @@ -6202,6 +6266,12 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal("Animal", operation.Table); }, o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_Id", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => { var operation = Assert.IsType(o); Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); diff --git a/test/EFCore.Specification.Tests/Query/InheritanceRelationshipsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/InheritanceRelationshipsQueryTestBase.cs index 3a2cd126e76..3aac06616a6 100644 --- a/test/EFCore.Specification.Tests/Query/InheritanceRelationshipsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/InheritanceRelationshipsQueryTestBase.cs @@ -68,7 +68,7 @@ public virtual void Entity_can_make_separate_relationships_with_base_type_and_de Assert.Equal(nameof(BaseReferenceOnDerived.BaseParent), fkOnBase.DependentToPrincipal.Name); Assert.Equal(nameof(DerivedInheritanceRelationshipEntity.BaseReferenceOnDerived), fkOnBase.PrincipalToDependent.Name); - var fkOnDerived = derivedDependentEntityType.GetDeclaredForeignKeys().Single(); + var fkOnDerived = derivedDependentEntityType.GetDeclaredForeignKeys().Single(fk => fk.PrincipalEntityType != dependentEntityType); Assert.NotSame(fkOnBase, fkOnDerived); Assert.Equal(principalEntityType, fkOnDerived.PrincipalEntityType); Assert.Equal(derivedDependentEntityType, fkOnDerived.DeclaringEntityType); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 8ac619c670c..6e25ff0eadb 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -949,7 +949,7 @@ public override async Task Using_from_sql_on_owner_generates_join_with_table_for await base.Using_from_sql_on_owner_generates_join_with_table_for_owned_shared_dependents(async); AssertSql( - @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [t].[Id], [t].[PersonAddress_AddressLine], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[Id1], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t3].[Id], [t3].[BranchAddress_BranchName], [t3].[BranchAddress_PlaceType], [t8].[Id], [t8].[BranchAddress_Country_Name], [t8].[BranchAddress_Country_PlanetId], [t12].[Id], [t12].[LeafBAddress_LeafBType], [t12].[LeafBAddress_PlaceType], [t17].[Id], [t17].[LeafBAddress_Country_Name], [t17].[LeafBAddress_Country_PlanetId], [t19].[Id], [t19].[LeafAAddress_LeafType], [t19].[LeafAAddress_PlaceType], [t19].[Id1], [t19].[LeafAAddress_Country_Name], [t19].[LeafAAddress_Country_PlanetId], [t].[Id0], [t3].[Id0], [t8].[Id0], [t8].[Id00], [t12].[Id0], [t17].[Id0], [t17].[Id00], [t19].[Id0], [o22].[ClientId], [o22].[Id], [o22].[OrderDate] + @"SELECT [o].[Id], [o].[Discriminator], [o].[Name], [t].[Id], [t].[PersonAddress_AddressLine], [t].[PersonAddress_PlaceType], [t].[PersonAddress_ZipCode], [t].[Id1], [t].[PersonAddress_Country_Name], [t].[PersonAddress_Country_PlanetId], [t1].[Id], [t1].[BranchAddress_BranchName], [t1].[BranchAddress_PlaceType], [t1].[Id1], [t1].[BranchAddress_Country_Name], [t1].[BranchAddress_Country_PlanetId], [t3].[Id], [t3].[LeafBAddress_LeafBType], [t3].[LeafBAddress_PlaceType], [t3].[Id1], [t3].[LeafBAddress_Country_Name], [t3].[LeafBAddress_Country_PlanetId], [t5].[Id], [t5].[LeafAAddress_LeafType], [t5].[LeafAAddress_PlaceType], [t5].[Id1], [t5].[LeafAAddress_Country_Name], [t5].[LeafAAddress_Country_PlanetId], [t].[Id0], [t1].[Id0], [t3].[Id0], [t5].[Id0], [o8].[ClientId], [o8].[Id], [o8].[OrderDate] FROM ( SELECT * FROM ""OwnedPerson"" ) AS [o] @@ -960,111 +960,37 @@ FROM [OwnedPerson] AS [o0] WHERE [o0].[PersonAddress_ZipCode] IS NOT NULL ) AS [t] ON [o].[Id] = [t].[Id] LEFT JOIN ( - SELECT [t1].[Id], [t1].[BranchAddress_BranchName], [t1].[BranchAddress_PlaceType], [t2].[Id] AS [Id0] - FROM ( - SELECT [o2].[Id], [o2].[BranchAddress_BranchName], [o2].[BranchAddress_PlaceType] - FROM [OwnedPerson] AS [o2] - WHERE [o2].[BranchAddress_PlaceType] IS NOT NULL OR [o2].[BranchAddress_BranchName] IS NOT NULL - UNION - SELECT [o3].[Id], [o3].[BranchAddress_BranchName], [o3].[BranchAddress_PlaceType] + SELECT [o2].[Id], [o2].[BranchAddress_BranchName], [o2].[BranchAddress_PlaceType], [t0].[Id] AS [Id0], [o2].[Id] AS [Id1], [o2].[BranchAddress_Country_Name], [o2].[BranchAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o2] + INNER JOIN ( + SELECT [o3].[Id] FROM [OwnedPerson] AS [o3] - INNER JOIN ( - SELECT [o4].[Id], [o4].[BranchAddress_Country_Name], [o4].[BranchAddress_Country_PlanetId] - FROM [OwnedPerson] AS [o4] - WHERE [o4].[BranchAddress_Country_PlanetId] IS NOT NULL - ) AS [t0] ON [o3].[Id] = [t0].[Id] - ) AS [t1] + WHERE [o3].[Discriminator] IN (N'Branch', N'LeafA') + ) AS [t0] ON [o2].[Id] = [t0].[Id] + WHERE [o2].[BranchAddress_PlaceType] IS NOT NULL OR [o2].[BranchAddress_BranchName] IS NOT NULL +) AS [t1] ON [o].[Id] = [t1].[Id] +LEFT JOIN ( + SELECT [o4].[Id], [o4].[LeafBAddress_LeafBType], [o4].[LeafBAddress_PlaceType], [t2].[Id] AS [Id0], [o4].[Id] AS [Id1], [o4].[LeafBAddress_Country_Name], [o4].[LeafBAddress_Country_PlanetId] + FROM [OwnedPerson] AS [o4] INNER JOIN ( SELECT [o5].[Id] FROM [OwnedPerson] AS [o5] - WHERE [o5].[Discriminator] IN (N'Branch', N'LeafA') - ) AS [t2] ON [t1].[Id] = [t2].[Id] + WHERE [o5].[Discriminator] = N'LeafB' + ) AS [t2] ON [o4].[Id] = [t2].[Id] + WHERE [o4].[LeafBAddress_PlaceType] IS NOT NULL OR [o4].[LeafBAddress_LeafBType] IS NOT NULL ) AS [t3] ON [o].[Id] = [t3].[Id] LEFT JOIN ( - SELECT [o6].[Id], [o6].[BranchAddress_Country_Name], [o6].[BranchAddress_Country_PlanetId], [t7].[Id] AS [Id0], [t7].[Id0] AS [Id00] + SELECT [o6].[Id], [o6].[LeafAAddress_LeafType], [o6].[LeafAAddress_PlaceType], [t4].[Id] AS [Id0], [o6].[Id] AS [Id1], [o6].[LeafAAddress_Country_Name], [o6].[LeafAAddress_Country_PlanetId] FROM [OwnedPerson] AS [o6] INNER JOIN ( - SELECT [t5].[Id], [t6].[Id] AS [Id0] - FROM ( - SELECT [o7].[Id], [o7].[BranchAddress_BranchName], [o7].[BranchAddress_PlaceType] - FROM [OwnedPerson] AS [o7] - WHERE [o7].[BranchAddress_PlaceType] IS NOT NULL OR [o7].[BranchAddress_BranchName] IS NOT NULL - UNION - SELECT [o8].[Id], [o8].[BranchAddress_BranchName], [o8].[BranchAddress_PlaceType] - FROM [OwnedPerson] AS [o8] - INNER JOIN ( - SELECT [o9].[Id], [o9].[BranchAddress_Country_Name], [o9].[BranchAddress_Country_PlanetId] - FROM [OwnedPerson] AS [o9] - WHERE [o9].[BranchAddress_Country_PlanetId] IS NOT NULL - ) AS [t4] ON [o8].[Id] = [t4].[Id] - ) AS [t5] - INNER JOIN ( - SELECT [o10].[Id] - FROM [OwnedPerson] AS [o10] - WHERE [o10].[Discriminator] IN (N'Branch', N'LeafA') - ) AS [t6] ON [t5].[Id] = [t6].[Id] - ) AS [t7] ON [o6].[Id] = [t7].[Id] - WHERE [o6].[BranchAddress_Country_PlanetId] IS NOT NULL -) AS [t8] ON [t3].[Id] = [t8].[Id] -LEFT JOIN ( - SELECT [t10].[Id], [t10].[LeafBAddress_LeafBType], [t10].[LeafBAddress_PlaceType], [t11].[Id] AS [Id0] - FROM ( - SELECT [o11].[Id], [o11].[LeafBAddress_LeafBType], [o11].[LeafBAddress_PlaceType] - FROM [OwnedPerson] AS [o11] - WHERE [o11].[LeafBAddress_PlaceType] IS NOT NULL OR [o11].[LeafBAddress_LeafBType] IS NOT NULL - UNION - SELECT [o12].[Id], [o12].[LeafBAddress_LeafBType], [o12].[LeafBAddress_PlaceType] - FROM [OwnedPerson] AS [o12] - INNER JOIN ( - SELECT [o13].[Id], [o13].[LeafBAddress_Country_Name], [o13].[LeafBAddress_Country_PlanetId] - FROM [OwnedPerson] AS [o13] - WHERE [o13].[LeafBAddress_Country_PlanetId] IS NOT NULL - ) AS [t9] ON [o12].[Id] = [t9].[Id] - ) AS [t10] - INNER JOIN ( - SELECT [o14].[Id] - FROM [OwnedPerson] AS [o14] - WHERE [o14].[Discriminator] = N'LeafB' - ) AS [t11] ON [t10].[Id] = [t11].[Id] -) AS [t12] ON [o].[Id] = [t12].[Id] -LEFT JOIN ( - SELECT [o15].[Id], [o15].[LeafBAddress_Country_Name], [o15].[LeafBAddress_Country_PlanetId], [t16].[Id] AS [Id0], [t16].[Id0] AS [Id00] - FROM [OwnedPerson] AS [o15] - INNER JOIN ( - SELECT [t14].[Id], [t15].[Id] AS [Id0] - FROM ( - SELECT [o16].[Id], [o16].[LeafBAddress_LeafBType], [o16].[LeafBAddress_PlaceType] - FROM [OwnedPerson] AS [o16] - WHERE [o16].[LeafBAddress_PlaceType] IS NOT NULL OR [o16].[LeafBAddress_LeafBType] IS NOT NULL - UNION - SELECT [o17].[Id], [o17].[LeafBAddress_LeafBType], [o17].[LeafBAddress_PlaceType] - FROM [OwnedPerson] AS [o17] - INNER JOIN ( - SELECT [o18].[Id], [o18].[LeafBAddress_Country_Name], [o18].[LeafBAddress_Country_PlanetId] - FROM [OwnedPerson] AS [o18] - WHERE [o18].[LeafBAddress_Country_PlanetId] IS NOT NULL - ) AS [t13] ON [o17].[Id] = [t13].[Id] - ) AS [t14] - INNER JOIN ( - SELECT [o19].[Id] - FROM [OwnedPerson] AS [o19] - WHERE [o19].[Discriminator] = N'LeafB' - ) AS [t15] ON [t14].[Id] = [t15].[Id] - ) AS [t16] ON [o15].[Id] = [t16].[Id] - WHERE [o15].[LeafBAddress_Country_PlanetId] IS NOT NULL -) AS [t17] ON [t12].[Id] = [t17].[Id] -LEFT JOIN ( - SELECT [o20].[Id], [o20].[LeafAAddress_LeafType], [o20].[LeafAAddress_PlaceType], [t18].[Id] AS [Id0], [o20].[Id] AS [Id1], [o20].[LeafAAddress_Country_Name], [o20].[LeafAAddress_Country_PlanetId] - FROM [OwnedPerson] AS [o20] - INNER JOIN ( - SELECT [o21].[Id] - FROM [OwnedPerson] AS [o21] - WHERE [o21].[Discriminator] = N'LeafA' - ) AS [t18] ON [o20].[Id] = [t18].[Id] - WHERE [o20].[LeafAAddress_LeafType] IS NOT NULL -) AS [t19] ON [o].[Id] = [t19].[Id] -LEFT JOIN [Order] AS [o22] ON [o].[Id] = [o22].[ClientId] -ORDER BY [o].[Id], [t].[Id], [t].[Id0], [t3].[Id], [t3].[Id0], [t8].[Id], [t8].[Id0], [t8].[Id00], [t12].[Id], [t12].[Id0], [t17].[Id], [t17].[Id0], [t17].[Id00], [t19].[Id], [t19].[Id0], [o22].[ClientId], [o22].[Id]"); + SELECT [o7].[Id] + FROM [OwnedPerson] AS [o7] + WHERE [o7].[Discriminator] = N'LeafA' + ) AS [t4] ON [o6].[Id] = [t4].[Id] + WHERE [o6].[LeafAAddress_LeafType] IS NOT NULL +) AS [t5] ON [o].[Id] = [t5].[Id] +LEFT JOIN [Order] AS [o8] ON [o].[Id] = [o8].[ClientId] +ORDER BY [o].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0], [t3].[Id], [t3].[Id0], [t5].[Id], [t5].[Id0], [o8].[ClientId], [o8].[Id]"); } private void AssertSql(params string[] expected) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs index 367072ad212..86f3bc0a6b1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTInheritanceQuerySqlServerTest.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; using Xunit.Abstractions; // ReSharper disable InconsistentNaming diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs index 6ea622aec8e..bfd387eb7a3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs @@ -177,6 +177,13 @@ public override void Can_change_dependent_instance_non_derived() base.Can_change_dependent_instance_non_derived(); AssertSql( + @"@p0='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) +@p1='Repair' (Size = 4000) + +SET NOCOUNT ON; +INSERT INTO [LicensedOperators] ([VehicleName], [LicenseType]) +VALUES (@p0, @p1);", + // @"@p1='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) @p0='repairman' (Size = 4000) @@ -184,13 +191,6 @@ public override void Can_change_dependent_instance_non_derived() UPDATE [Vehicles] SET [Operator_Name] = @p0 WHERE [Name] = @p1; SELECT @@ROWCOUNT;", - // - @"@p2='Trek Pro Fit Madone 6 Series' (Nullable = false) (Size = 450) -@p3='Repair' (Size = 4000) - -SET NOCOUNT ON; -INSERT INTO [LicensedOperators] ([VehicleName], [LicenseType]) -VALUES (@p2, @p3);", // @"SELECT TOP(2) [v].[Name], [v].[SeatingCapacity], CASE WHEN [p].[Name] IS NOT NULL THEN N'PoweredVehicle' diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index ca98643251b..179a7f90c88 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -1015,6 +1015,12 @@ public void Noop_TPT_with_FKs_and_seed_data() source.Entity("Cat", b => { + b.HasOne("Animal", null) + .WithOne() + .HasForeignKey("Cat", "Id") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + b.HasOne("Animal", null) .WithMany() .HasForeignKey("PreyId"); @@ -1022,10 +1028,25 @@ public void Noop_TPT_with_FKs_and_seed_data() source.Entity("Dog", b => { + b.HasOne("Animal", null) + .WithOne() + .HasForeignKey("Dog", "Id") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + b.HasOne("Animal", null) .WithMany() .HasForeignKey("PreyId"); }); + + source.Entity("Mouse", b => + { + b.HasOne("Animal", null) + .WithOne() + .HasForeignKey("Mouse", "Id") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); }, modelBuilder => { diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 9e7cd2f83fc..3ac4b7a3d98 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -883,15 +883,16 @@ public virtual void Detects_owned_entity_type_without_ownership() public virtual void Detects_ForeignKey_on_inherited_generated_key_property() { var modelBuilder = CreateConventionalModelBuilder(); - modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); - modelBuilder.Entity>().HasOne().WithOne().HasForeignKey>(e => e.Id); + modelBuilder.Entity().Property("SomeId").ValueGeneratedOnAdd(); + modelBuilder.Entity().HasAlternateKey("SomeId"); + modelBuilder.Entity>().HasOne().WithOne().HasForeignKey>("SomeId"); modelBuilder.Entity>(); VerifyError( CoreStrings.ForeignKeyPropertyInKey( - nameof(Abstract.Id), + "SomeId", "Generic", - "{'" + nameof(Abstract.Id) + "'}", + "{'SomeId'}", nameof(Abstract)), modelBuilder.Model); } diff --git a/test/EFCore.Tests/Metadata/Conventions/ValueGeneratorConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ValueGeneratorConventionTest.cs index e8293fbbfdc..cfdefec16a8 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ValueGeneratorConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ValueGeneratorConventionTest.cs @@ -447,29 +447,23 @@ public void Identity_is_added_when_foreign_key_is_removed_and_key_is_primary_key #endregion private static void RunConvention(InternalEntityTypeBuilder entityBuilder) - { - new ValueGenerationConvention(CreateDependencies()) + => new ValueGenerationConvention(CreateDependencies()) .ProcessEntityTypePrimaryKeyChanged( entityBuilder, entityBuilder.Metadata.FindPrimaryKey(), null, new ConventionContext(entityBuilder.Metadata.Model.ConventionDispatcher)); - } private static void RunConvention(InternalForeignKeyBuilder foreignKeyBuilder) - { - new ValueGenerationConvention(CreateDependencies()) + => new ValueGenerationConvention(CreateDependencies()) .ProcessForeignKeyAdded( foreignKeyBuilder, new ConventionContext( foreignKeyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher)); - } private static void RunConvention(InternalEntityTypeBuilder entityBuilder, ForeignKey foreignKey) - { - new ValueGenerationConvention(CreateDependencies()) + => new ValueGenerationConvention(CreateDependencies()) .ProcessForeignKeyRemoved( entityBuilder, foreignKey, new ConventionContext(entityBuilder.Metadata.Model.ConventionDispatcher)); - } private static ProviderConventionSetBuilderDependencies CreateDependencies() => InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService(); diff --git a/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs b/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs index 6375d48c916..90d3456a659 100644 --- a/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ForeignKeyTest.cs @@ -282,8 +282,7 @@ private IMutableForeignKey CreateOneToManySameBaseFK() var dependentEntityType = model.AddEntityType(typeof(OneToManyDependent)); dependentEntityType.BaseType = baseEntityType; - var fkProp = dependentEntityType.AddProperty("Fk", typeof(int)); - var fk = dependentEntityType.AddForeignKey(new[] { fkProp }, pk, principalEntityType); + var fk = dependentEntityType.AddForeignKey(new[] { property1 }, pk, principalEntityType); fk.SetPrincipalToDependent(NavigationBase.OneToManyDependentsProperty); fk.SetDependentToPrincipal(NavigationBase.OneToManyPrincipalProperty); return fk; @@ -299,8 +298,7 @@ private IMutableForeignKey CreateOneToManySameHierarchyFK() var dependentEntityType = model.AddEntityType(typeof(OneToManyDependent)); dependentEntityType.BaseType = baseEntityType; - var fkProp = dependentEntityType.AddProperty("Fk", typeof(int)); - var fk = dependentEntityType.AddForeignKey(new[] { fkProp }, pk, baseEntityType); + var fk = dependentEntityType.AddForeignKey(new[] { property1 }, pk, baseEntityType); fk.SetPrincipalToDependent(NavigationBase.OneToManyDependentsProperty); return fk; } @@ -417,14 +415,13 @@ private IMutableForeignKey CreateSelfRefFK(bool useAltKey = false) { var entityType = CreateModel().AddEntityType(typeof(SelfRef)); var pk = entityType.SetPrimaryKey(entityType.AddProperty(SelfRef.IdProperty)); - var fkProp = entityType.AddProperty(SelfRef.SelfRefIdProperty); var property = entityType.AddProperty("AltId", typeof(int)); var principalKey = useAltKey ? entityType.AddKey(property) : pk; - var fk = entityType.AddForeignKey(new[] { fkProp }, principalKey, entityType); + var fk = entityType.AddForeignKey(new[] { pk.Properties.Single() }, principalKey, entityType); fk.IsUnique = true; fk.SetDependentToPrincipal(SelfRef.SelfRefPrincipalProperty); fk.SetPrincipalToDependent(SelfRef.SelfRefDependentProperty); @@ -485,35 +482,35 @@ public void IsSelfReferencing_returns_false_for_non_hierarchical_foreign_keys() } [ConditionalFact] - public void IsSelfPrimaryKeyReferencing_returns_true_for_self_ref_foreign_keys() + public void IsBaseLinking_returns_true_for_self_ref_foreign_keys() { var fk = CreateSelfRefFK(); - Assert.True(fk.IsSelfPrimaryKeyReferencing()); + Assert.True(fk.IsBaseLinking()); } [ConditionalFact] - public void IsSelfPrimaryKeyReferencing_returns_false_for_non_pk_self_ref_foreign_keys() + public void IsBaseLinking_returns_false_for_non_pk_self_ref_foreign_keys() { var fk = CreateSelfRefFK(useAltKey: true); - Assert.False(fk.IsSelfPrimaryKeyReferencing()); + Assert.False(fk.IsBaseLinking()); } [ConditionalFact] - public void IsSelfPrimaryKeyReferencing_returns_true_for_same_hierarchy_foreign_keys() + public void IsBaseLinking_returns_true_for_same_hierarchy_foreign_keys() { var fk = CreateOneToManySameHierarchyFK(); - Assert.True(fk.IsSelfPrimaryKeyReferencing()); + Assert.True(fk.IsBaseLinking()); } [ConditionalFact] - public void IsSelfPrimaryKeyReferencing_returns_true_for_same_base_foreign_keys() + public void IsBaseLinking_returns_true_for_same_base_foreign_keys() { var fk = CreateOneToManySameBaseFK(); - Assert.True(fk.IsSelfPrimaryKeyReferencing()); + Assert.True(fk.IsBaseLinking()); } [ConditionalFact] @@ -521,7 +518,7 @@ public void IsSelfPrimaryKeyReferencing_returns_false_for_non_hierarchical_forei { var fk = CreateOneToManyFK(); - Assert.False(fk.IsSelfPrimaryKeyReferencing()); + Assert.False(fk.IsBaseLinking()); } [ConditionalFact]