From e567b4da3a6abd3b40fd4de65642b6999a6615f1 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 19 Aug 2020 19:34:58 -0700 Subject: [PATCH] Add FK constraints between TPT tables (#22136) 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 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, 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 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, 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 class RelationalForeignKeyExtensions 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 class RelationalForeignKeyExtensions 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 @@ bool HasSiblings(IEntityType entityType) // 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 @@ public CommandBatchPreparer([NotNull] CommandBatchPreparerDependencies dependenc 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 @@ public CommandBatchPreparer([NotNull] CommandBatchPreparerDependencies dependenc 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 Format(IIndex index, ModificationCommand source, ModificationComman { 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 Format(IIndex index, ModificationCommand source, ModificationComman 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 class 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([NotNu } 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 @@ private Type FindCandidateNavigationPropertyType(PropertyInfo propertyInfo) 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 @@ private bool Contains(IForeignKey inheritedFk, IForeignKey derivedFk) 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 @@ public ForeignKeyPropertyDiscoveryConvention([NotNull] ProviderConventionSetBuil || 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 ValueGenerationConvention([NotNull] ProviderConventionSetBuilderDependenc { OnForeignKeyRemoved(oldDependentProperties); - if (relationshipBuilder.Metadata.Builder != null) + if (relationshipBuilder.Metadata.Builder != null + && !foreignKey.IsBaseLinking()) { foreach (var property in foreignKey.Properties) { @@ -179,7 +184,7 @@ private void OnForeignKeyRemoved(IReadOnlyList foreignKeyPr /// 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 static InternalIndexBuilder DetachIndex(Index indexToDetach) principalKey: null, propertyBaseName: navigationProperty?.GetSimpleMemberName(), required: required, - configurationSource: configurationSource); + configurationSource); } else { @@ -2812,7 +2812,7 @@ private static InternalIndexBuilder DetachIndex(Index indexToDetach) principalKey: null, propertyBaseName: navigationProperty?.GetSimpleMemberName(), required: null, - configurationSource: configurationSource); + configurationSource); } relationship = newRelationship; @@ -3884,8 +3884,8 @@ public virtual bool ShouldReuniquifyTemporaryProperties([NotNull] ForeignKey for 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 @@ public virtual bool CanSetForeignKey([CanBeNull] IReadOnlyList propert // 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 @@ public override async Task Using_from_sql_on_owner_generates_join_with_table_for 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]