Skip to content

Commit bf62eaf

Browse files
authored
Don't skip creating cascade delete on SQL Server when types are base-linking across tables (#28965)
1 parent e137569 commit bf62eaf

File tree

9 files changed

+47
-37
lines changed

9 files changed

+47
-37
lines changed

src/EFCore.SqlServer/Metadata/Conventions/SqlServerOnDeleteConvention.cs

+9-5
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,6 @@ protected override DeleteBehavior GetTargetDeleteBehavior(IConventionForeignKey
5757
return deleteBehavior;
5858
}
5959

60-
if (foreignKey.IsBaseLinking())
61-
{
62-
return DeleteBehavior.ClientCascade;
63-
}
64-
6560
return ProcessSkipNavigations(foreignKey.GetReferencingSkipNavigations()) ?? deleteBehavior;
6661
}
6762

@@ -133,6 +128,15 @@ public virtual void ProcessEntityTypeAnnotationChanged(
133128
|| name == RelationalAnnotationNames.Schema)
134129
{
135130
ProcessSkipNavigations(entityTypeBuilder.Metadata.GetDeclaredSkipNavigations());
131+
132+
foreach (var foreignKey in entityTypeBuilder.Metadata.GetDeclaredForeignKeys())
133+
{
134+
var deleteBehavior = GetTargetDeleteBehavior(foreignKey);
135+
if (foreignKey.DeleteBehavior != deleteBehavior)
136+
{
137+
foreignKey.Builder.OnDelete(deleteBehavior);
138+
}
139+
}
136140
}
137141
}
138142
}

test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT()
538538
b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", null)
539539
.WithOne()
540540
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", ""Id"")
541-
.OnDelete(DeleteBehavior.ClientCascade)
541+
.OnDelete(DeleteBehavior.Cascade)
542542
.IsRequired();
543543
});"),
544544
model =>
@@ -606,7 +606,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu
606606
b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", null)
607607
.WithOne()
608608
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", ""Id"")
609-
.OnDelete(DeleteBehavior.ClientCascade)
609+
.OnDelete(DeleteBehavior.Cascade)
610610
.IsRequired();
611611
});"),
612612
o =>
@@ -832,7 +832,7 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables()
832832
b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order"", null)
833833
.WithOne()
834834
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order"", ""Id"")
835-
.OnDelete(DeleteBehavior.ClientCascade)
835+
.OnDelete(DeleteBehavior.Cascade)
836836
.IsRequired();
837837
838838
b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", ""OrderBillingDetails"", b1 =>
@@ -863,7 +863,7 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables()
863863
b1.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order.OrderBillingDetails#Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", null)
864864
.WithOne()
865865
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order.OrderBillingDetails#Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", ""OrderId"")
866-
.OnDelete(DeleteBehavior.ClientCascade)
866+
.OnDelete(DeleteBehavior.Cascade)
867867
.IsRequired();
868868
869869
b1.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+StreetAddress"", ""StreetAddress"", b2 =>
@@ -912,7 +912,7 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables()
912912
b1.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order.OrderShippingDetails#Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", null)
913913
.WithOne()
914914
.HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+Order.OrderShippingDetails#Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", ""OrderId"")
915-
.OnDelete(DeleteBehavior.ClientCascade)
915+
.OnDelete(DeleteBehavior.Cascade)
916916
.IsRequired();
917917
918918
b1.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+StreetAddress"", ""StreetAddress"", b2 =>

test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1340,7 +1340,7 @@ public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEnt
13401340
var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId"")!, declaringEntityType.FindProperty(""PrincipalBaseAlternateId"")! },
13411341
principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""PrincipalBaseId"")!, principalEntityType.FindProperty(""PrincipalBaseAlternateId"")! })!,
13421342
principalEntityType,
1343-
deleteBehavior: DeleteBehavior.ClientCascade,
1343+
deleteBehavior: DeleteBehavior.Cascade,
13441344
unique: true,
13451345
required: true,
13461346
requiredDependent: true);
@@ -1681,7 +1681,7 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt
16811681
var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""Id"")!, declaringEntityType.FindProperty(""AlternateId"")! },
16821682
principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!,
16831683
principalEntityType,
1684-
deleteBehavior: DeleteBehavior.ClientCascade,
1684+
deleteBehavior: DeleteBehavior.Cascade,
16851685
unique: true,
16861686
required: true);
16871687
@@ -1967,7 +1967,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
19671967
Assert.True(tptForeignKey.IsUnique);
19681968
Assert.Null(tptForeignKey.DependentToPrincipal);
19691969
Assert.Null(tptForeignKey.PrincipalToDependent);
1970-
Assert.Equal(DeleteBehavior.ClientCascade, tptForeignKey.DeleteBehavior);
1970+
Assert.Equal(DeleteBehavior.Cascade, tptForeignKey.DeleteBehavior);
19711971
Assert.Equal(principalKey.Properties, tptForeignKey.Properties);
19721972
Assert.Same(principalKey, tptForeignKey.PrincipalKey);
19731973

test/EFCore.Relational.Specification.Tests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesTestBase.cs

-12
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,6 @@ public override Task Delete_GroupBy_Where_Select_First_3(bool async)
3434
RelationalStrings.ExecuteOperationOnTPT("ExecuteDelete", "Animal"),
3535
() => base.Delete_GroupBy_Where_Select_First_3(async));
3636

37-
[ConditionalTheory(Skip = "Issue#28532")]
38-
public override Task Delete_where_using_hierarchy(bool async)
39-
{
40-
return base.Delete_where_using_hierarchy(async);
41-
}
42-
43-
[ConditionalTheory(Skip = "Issue#28532")]
44-
public override Task Delete_where_using_hierarchy_derived(bool async)
45-
{
46-
return base.Delete_where_using_hierarchy_derived(async);
47-
}
48-
4937
// Keyless entities are mapped as TPH only
5038
public override Task Update_where_keyless_entity_mapped_to_sql_query(bool async) => Task.CompletedTask;
5139

test/EFCore.Relational.Specification.Tests/Query/TPTGearsOfWarQueryRelationalFixture.cs

+6
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
2828
modelBuilder.Entity<LocustHorde>().ToTable("LocustHordes");
2929

3030
modelBuilder.Entity<LocustCommander>().ToTable("LocustCommanders");
31+
32+
modelBuilder.Entity<Squad>()
33+
.HasMany(s => s.Members)
34+
.WithOne(g => g.Squad)
35+
.HasForeignKey(g => g.SquadId)
36+
.OnDelete(DeleteBehavior.ClientCascade);
3137
}
3238
}

test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -857,11 +857,13 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
857857
Assert.Equal("FK_SpecialCustomer_Customer_Id", specialCustomerTptFkConstraint.Name);
858858
Assert.NotNull(specialCustomerTptFkConstraint.MappedForeignKeys.Single());
859859
Assert.Same(customerTable, specialCustomerTptFkConstraint.PrincipalTable);
860+
Assert.Equal(ReferentialAction.Cascade, specialCustomerTptFkConstraint.OnDeleteAction);
860861

861862
var anotherSpecialCustomerFkConstraint = foreignKeys[2];
862863
Assert.Equal("FK_SpecialCustomer_SpecialCustomer_AnotherRelatedCustomerId", anotherSpecialCustomerFkConstraint.Name);
863864
Assert.NotNull(anotherSpecialCustomerFkConstraint.MappedForeignKeys.Single());
864865
Assert.Same(specialCustomerTable, anotherSpecialCustomerFkConstraint.PrincipalTable);
866+
Assert.Equal(ReferentialAction.Cascade, specialCustomerTptFkConstraint.OnDeleteAction);
865867

866868
Assert.Equal(new[] { orderCustomerFkConstraint, specialCustomerTptFkConstraint }, customerTable.ReferencingForeignKeyConstraints);
867869

test/EFCore.SqlServer.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqlServerTest.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ FROM [Countries] AS [c]
3939
WHERE (
4040
SELECT COUNT(*)
4141
FROM [Animals] AS [a]
42+
LEFT JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
43+
LEFT JOIN [Eagle] AS [e] ON [a].[Id] = [e].[Id]
44+
LEFT JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
4245
WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [a].[CountryId] > 0) > 0");
4346
}
4447

@@ -52,7 +55,10 @@ FROM [Countries] AS [c]
5255
WHERE (
5356
SELECT COUNT(*)
5457
FROM [Animals] AS [a]
55-
WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] > 0) > 0");
58+
LEFT JOIN [Birds] AS [b] ON [a].[Id] = [b].[Id]
59+
LEFT JOIN [Eagle] AS [e] ON [a].[Id] = [e].[Id]
60+
LEFT JOIN [Kiwi] AS [k] ON [a].[Id] = [k].[Id]
61+
WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [k].[Id] IS NOT NULL AND [a].[CountryId] > 0) > 0");
5662
}
5763

5864
public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)

test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ public void Noop_TPT_with_FKs_and_seed_data()
10241024
b.HasOne("Animal", null)
10251025
.WithOne()
10261026
.HasForeignKey("Cat", "Id")
1027-
.OnDelete(DeleteBehavior.ClientCascade)
1027+
.OnDelete(DeleteBehavior.Cascade)
10281028
.IsRequired();
10291029

10301030
b.HasOne("Animal", null)
@@ -1038,7 +1038,7 @@ public void Noop_TPT_with_FKs_and_seed_data()
10381038
b.HasOne("Animal", null)
10391039
.WithOne()
10401040
.HasForeignKey("Dog", "Id")
1041-
.OnDelete(DeleteBehavior.ClientCascade)
1041+
.OnDelete(DeleteBehavior.Cascade)
10421042
.IsRequired();
10431043

10441044
b.HasOne("Animal", null)
@@ -1052,7 +1052,7 @@ public void Noop_TPT_with_FKs_and_seed_data()
10521052
b.HasOne("Animal", null)
10531053
.WithOne()
10541054
.HasForeignKey("Mouse", "Id")
1055-
.OnDelete(DeleteBehavior.ClientCascade)
1055+
.OnDelete(DeleteBehavior.Cascade)
10561056
.IsRequired();
10571057
});
10581058
},

test/EFCore.Sqlite.FunctionalTests/BulkUpdates/TPTFiltersInheritanceBulkUpdatesSqliteTest.cs

+12-8
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,29 @@ public override async Task Delete_where_using_hierarchy(bool async)
3434
await base.Delete_where_using_hierarchy(async);
3535

3636
AssertSql(
37-
@"DELETE FROM [c]
38-
FROM [Countries] AS [c]
37+
@"DELETE FROM ""Countries"" AS ""c""
3938
WHERE (
4039
SELECT COUNT(*)
41-
FROM [Animals] AS [a]
42-
WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [a].[CountryId] > 0) > 0");
40+
FROM ""Animals"" AS ""a""
41+
LEFT JOIN ""Birds"" AS ""b"" ON ""a"".""Id"" = ""b"".""Id""
42+
LEFT JOIN ""Eagle"" AS ""e"" ON ""a"".""Id"" = ""e"".""Id""
43+
LEFT JOIN ""Kiwi"" AS ""k"" ON ""a"".""Id"" = ""k"".""Id""
44+
WHERE ""a"".""CountryId"" = 1 AND ""c"".""Id"" = ""a"".""CountryId"" AND ""a"".""CountryId"" > 0) > 0");
4345
}
4446

4547
public override async Task Delete_where_using_hierarchy_derived(bool async)
4648
{
4749
await base.Delete_where_using_hierarchy_derived(async);
4850

4951
AssertSql(
50-
@"DELETE FROM [c]
51-
FROM [Countries] AS [c]
52+
@"DELETE FROM ""Countries"" AS ""c""
5253
WHERE (
5354
SELECT COUNT(*)
54-
FROM [Animals] AS [a]
55-
WHERE [a].[CountryId] = 1 AND [c].[Id] = [a].[CountryId] AND [a].[Discriminator] = N'Kiwi' AND [a].[CountryId] > 0) > 0");
55+
FROM ""Animals"" AS ""a""
56+
LEFT JOIN ""Birds"" AS ""b"" ON ""a"".""Id"" = ""b"".""Id""
57+
LEFT JOIN ""Eagle"" AS ""e"" ON ""a"".""Id"" = ""e"".""Id""
58+
LEFT JOIN ""Kiwi"" AS ""k"" ON ""a"".""Id"" = ""k"".""Id""
59+
WHERE ""a"".""CountryId"" = 1 AND ""c"".""Id"" = ""a"".""CountryId"" AND ""k"".""Id"" IS NOT NULL AND ""a"".""CountryId"" > 0) > 0");
5660
}
5761

5862
public override async Task Delete_where_keyless_entity_mapped_to_sql_query(bool async)

0 commit comments

Comments
 (0)