Skip to content

Commit

Permalink
Validate that if any fragments use table sharing then the main fragme…
Browse files Browse the repository at this point in the history
…nts are mapped to the same table

Fixes #29104
  • Loading branch information
AndriySvyryd committed Oct 6, 2022
1 parent 96ce1d9 commit 748c596
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 206 deletions.
21 changes: 12 additions & 9 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2143,16 +2143,19 @@ protected virtual void ValidateMappingFragments(
entityType.DisplayName(), fragment.StoreObject.DisplayName()));
}

var unmatchedLeafRowInternalFk = entityType.FindRowInternalForeignKeys(fragment.StoreObject)
.FirstOrDefault(
fk => entityType.FindRowInternalForeignKeys(mainStoreObject.Value)
.All(mainFk => mainFk.PrincipalEntityType != fk.PrincipalEntityType));
if (unmatchedLeafRowInternalFk != null)
foreach (var foreignKey in entityType.FindRowInternalForeignKeys(fragment.StoreObject))
{
throw new InvalidOperationException(
RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(
entityType.DisplayName(), fragment.StoreObject.DisplayName(),
unmatchedLeafRowInternalFk.PrincipalEntityType.DisplayName()));
var principalMainFragment = StoreObjectIdentifier.Create(
foreignKey.PrincipalEntityType, fragment.StoreObject.StoreObjectType)!.Value;
if (principalMainFragment != mainStoreObject)
{
throw new InvalidOperationException(
RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(
entityType.DisplayName(),
fragment.StoreObject.DisplayName(),
foreignKey.PrincipalEntityType.DisplayName(),
principalMainFragment.DisplayName()));
}
}

var propertiesFound = false;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@
<value>Entity type '{entityType}' has a split mapping for '{storeObject}', but it doesn't have a main mapping of the same type. Map '{entityType}' to '{storeObjectType}'.</value>
</data>
<data name="EntitySplittingUnmatchedMainTableSplitting" xml:space="preserve">
<value>Entity type '{entityType}' has a split mapping for '{storeObject}' that shares the table with '{principalEntityType}', but the main mappings of these types do not share a table. Map the split fragments of '{entityType}' to non-shared tables or map the main fragment to a table that '{principalEntityType}' is also mapped to.</value>
<value>Entity type '{entityType}' has a split mapping for '{storeObject}' that is shared with the entity type '{principalEntityType}', but the main mappings of these types do not share a table. Map the split fragments of '{entityType}' to non-shared tables or map the main fragment to '{principalStoreObject}'.</value>
</data>
<data name="ErrorMaterializingProperty" xml:space="preserve">
<value>An error occurred while reading a database value for property '{entityType}.{property}'. See the inner exception for more information.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -904,116 +904,6 @@ await AssertQuery(
entryCount: 10);
}

[ConditionalTheory(Skip = "Issue#29104")]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Split_entity_owning_a_split_reference_with_table_sharing_2(bool async)
{
await InitializeContextFactoryAsync(
mb =>
{
mb.Entity<EntityOne>(
b =>
{
b.ToTable("SplitEntityOnePart1");
b.SplitToTable("SplitEntityOnePart2",
tb =>
{
tb.Property(e => e.IntValue3);
tb.Property(e => e.StringValue3);
});
b.SplitToTable("SplitEntityOnePart3",
tb =>
{
tb.Property(e => e.IntValue4);
tb.Property(e => e.StringValue4);
});
b.OwnsOne(e => e.OwnedReference,
o =>
{
o.ToTable("SplitEntityOnePart2");
o.SplitToTable("SplitEntityOnePart3",
t =>
{
t.Property(e => e.OwnedIntValue3);
t.Property(e => e.OwnedStringValue3);
});
o.SplitToTable("SplitEntityOnePart1",
t =>
{
t.Property(e => e.OwnedIntValue4);
t.Property(e => e.OwnedStringValue4);
});
});
});
});

await AssertQuery(
async,
ss => ss.Set<EntityOne>(),
elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude<EntityOne>(i => i.OwnedReference)),
entryCount: 10);
}

[ConditionalTheory(Skip = "Issue#29104")]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Split_entity_owning_a_split_reference_with_table_sharing_3(bool async)
{
await InitializeContextFactoryAsync(
mb =>
{
mb.Entity<EntityOne>(
b =>
{
b.ToTable("SplitEntityOnePart1");
b.SplitToTable("SplitEntityOnePart2",
tb =>
{
tb.Property(e => e.IntValue3);
tb.Property(e => e.StringValue3);
});
b.SplitToTable("SplitEntityOnePart3",
tb =>
{
tb.Property(e => e.IntValue4);
tb.Property(e => e.StringValue4);
});
b.OwnsOne(e => e.OwnedReference,
o =>
{
o.ToTable("SplitEntityOnePart3");
o.SplitToTable("SplitEntityOnePart2",
t =>
{
t.Property(e => e.OwnedIntValue3);
t.Property(e => e.OwnedStringValue3);
});
o.SplitToTable("SplitEntityOnePart1",
t =>
{
t.Property(e => e.OwnedIntValue4);
t.Property(e => e.OwnedStringValue4);
});
});
});
});

await AssertQuery(
async,
ss => ss.Set<EntityOne>(),
elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude<EntityOne>(i => i.OwnedReference)),
entryCount: 10);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Split_entity_owning_a_split_reference_with_table_sharing_4(bool async)
Expand Down Expand Up @@ -1069,61 +959,6 @@ await AssertQuery(
entryCount: 10);
}

[ConditionalTheory(Skip = "Issue#29104")]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Split_entity_owning_a_split_reference_with_table_sharing_5(bool async)
{
await InitializeContextFactoryAsync(
mb =>
{
mb.Entity<EntityOne>(
b =>
{
b.ToTable("SplitEntityOnePart1");
b.SplitToTable("SplitEntityOnePart2",
tb =>
{
tb.Property(e => e.IntValue3);
tb.Property(e => e.StringValue3);
});
b.SplitToTable("SplitEntityOnePart3",
tb =>
{
tb.Property(e => e.IntValue4);
tb.Property(e => e.StringValue4);
});
b.OwnsOne(e => e.OwnedReference,
o =>
{
o.ToTable("SplitEntityOnePart2");
o.SplitToTable("SplitEntityOnePart1",
t =>
{
t.Property(e => e.OwnedIntValue3);
t.Property(e => e.OwnedStringValue3);
});
o.SplitToTable("OwnedReferencePart3",
t =>
{
t.Property(e => e.OwnedIntValue4);
t.Property(e => e.OwnedStringValue4);
});
});
});
});

await AssertQuery(
async,
ss => ss.Set<EntityOne>(),
elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude<EntityOne>(i => i.OwnedReference)),
entryCount: 10);
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Split_entity_owning_a_split_reference_with_table_sharing_6(bool async)
Expand Down Expand Up @@ -1155,7 +990,7 @@ await InitializeContextFactoryAsync(
{
o.ToTable("SplitEntityOnePart2");
o.SplitToTable("SplitEntityOnePart3",
o.SplitToTable("OwnedReferencePart2",
t =>
{
t.Property(e => e.OwnedIntValue3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,45 @@ public void Detects_entity_splitting_with_partial_table_splitting()
});

VerifyError(
RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(nameof(OrderDetails), "Order", nameof(Order)),
RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(nameof(OrderDetails), "Order", nameof(Order), "Order"),
modelBuilder);
}

[ConditionalFact]
public void Detects_entity_splitting_with_reverse_table_splitting()
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder.Entity<Order>(
cb =>
{
cb.Ignore(c => c.Customer);
cb.ToTable("Order");
cb.SplitToTable(
"OrderDetails", tb =>
{
tb.Property(c => c.PartitionId);
});
cb.OwnsOne(
c => c.OrderDetails, db =>
{
db.ToTable("OrderDetails");
db.Property<string>("OtherAddress");
db.SplitToTable(
"Order", tb =>
{
tb.Property("OtherAddress");
});
});
cb.Navigation(c => c.OrderDetails).IsRequired();
});

VerifyError(
RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(nameof(OrderDetails), "Order", nameof(Order), "Order"),
modelBuilder);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,20 +279,6 @@ FROM [SplitEntityOnePart1] AS [s]
INNER JOIN [SplitEntityOnePart2] AS [s1] ON [s].[Id] = [s1].[Id]");
}

public override async Task Split_entity_owning_a_split_reference_with_table_sharing_2(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_2(async);

AssertSql();
}

public override async Task Split_entity_owning_a_split_reference_with_table_sharing_3(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_3(async);

AssertSql();
}

public override async Task Split_entity_owning_a_split_reference_with_table_sharing_4(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_4(async);
Expand All @@ -305,19 +291,17 @@ FROM [SplitEntityOnePart1] AS [s]
LEFT JOIN [OwnedReferencePart3] AS [o] ON [s].[Id] = [o].[EntityOneId]");
}

public override async Task Split_entity_owning_a_split_reference_with_table_sharing_5(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_5(async);

AssertSql();
}

[ConditionalTheory(Skip = "Issue29075")]
public override async Task Split_entity_owning_a_split_reference_with_table_sharing_6(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_6(async);

AssertSql();
AssertSql(
@"SELECT [s].[Id], [s].[EntityThreeId], [s].[IntValue1], [s].[IntValue2], [s1].[IntValue3], [s0].[IntValue4], [s].[StringValue1], [s].[StringValue2], [s1].[StringValue3], [s0].[StringValue4], [s1].[Id], [s1].[OwnedReference_Id], [s1].[OwnedReference_OwnedIntValue1], [s1].[OwnedReference_OwnedIntValue2], [o0].[OwnedIntValue3], [o].[OwnedIntValue4], [s1].[OwnedReference_OwnedStringValue1], [s1].[OwnedReference_OwnedStringValue2], [o0].[OwnedStringValue3], [o].[OwnedStringValue4]
FROM [SplitEntityOnePart1] AS [s]
INNER JOIN [SplitEntityOnePart3] AS [s0] ON [s].[Id] = [s0].[Id]
INNER JOIN [SplitEntityOnePart2] AS [s1] ON [s].[Id] = [s1].[Id]
LEFT JOIN [OwnedReferencePart3] AS [o] ON [s1].[Id] = [o].[EntityOneId]
LEFT JOIN [OwnedReferencePart2] AS [o0] ON [s1].[Id] = [o0].[EntityOneId]");
}

public override async Task Tph_entity_owning_a_split_reference_on_base_with_table_sharing(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,18 @@ LEFT JOIN (
) AS ""t"" ON ""e"".""Id"" = ""t"".""EntityOneId""
ORDER BY ""e"".""Id"", ""t"".""EntityOneId""");
}

public override async Task Split_entity_owning_a_split_reference_with_table_sharing_6(bool async)
{
await base.Split_entity_owning_a_split_reference_with_table_sharing_6(async);

AssertSql(
@"SELECT ""s"".""Id"", ""s"".""EntityThreeId"", ""s"".""IntValue1"", ""s"".""IntValue2"", ""s1"".""IntValue3"", ""s0"".""IntValue4"", ""s"".""StringValue1"", ""s"".""StringValue2"", ""s1"".""StringValue3"", ""s0"".""StringValue4"", ""s1"".""Id"", ""s1"".""OwnedReference_Id"", ""s1"".""OwnedReference_OwnedIntValue1"", ""s1"".""OwnedReference_OwnedIntValue2"", ""s0"".""OwnedReference_OwnedIntValue3"", ""o"".""OwnedIntValue4"", ""s1"".""OwnedReference_OwnedStringValue1"", ""s1"".""OwnedReference_OwnedStringValue2"", ""s0"".""OwnedReference_OwnedStringValue3"", ""o"".""OwnedStringValue4""
@"SELECT ""s"".""Id"", ""s"".""EntityThreeId"", ""s"".""IntValue1"", ""s"".""IntValue2"", ""s1"".""IntValue3"", ""s0"".""IntValue4"", ""s"".""StringValue1"", ""s"".""StringValue2"", ""s1"".""StringValue3"", ""s0"".""StringValue4"", ""s1"".""Id"", ""s1"".""OwnedReference_Id"", ""s1"".""OwnedReference_OwnedIntValue1"", ""s1"".""OwnedReference_OwnedIntValue2"", ""o0"".""OwnedIntValue3"", ""o"".""OwnedIntValue4"", ""s1"".""OwnedReference_OwnedStringValue1"", ""s1"".""OwnedReference_OwnedStringValue2"", ""o0"".""OwnedStringValue3"", ""o"".""OwnedStringValue4""
FROM ""SplitEntityOnePart1"" AS ""s""
INNER JOIN ""SplitEntityOnePart3"" AS ""s0"" ON ""s"".""Id"" = ""s0"".""Id""
INNER JOIN ""SplitEntityOnePart2"" AS ""s1"" ON ""s"".""Id"" = ""s1"".""Id""
LEFT JOIN ""OwnedReferencePart3"" AS ""o"" ON ""s1"".""Id"" = ""o"".""EntityOneId""");
LEFT JOIN ""OwnedReferencePart3"" AS ""o"" ON ""s1"".""Id"" = ""o"".""EntityOneId""
LEFT JOIN ""OwnedReferencePart2"" AS ""o0"" ON ""s1"".""Id"" = ""o0"".""EntityOneId""");
}

public override async Task Tph_entity_owning_a_split_reference_on_base_without_table_sharing(bool async)
Expand Down

0 comments on commit 748c596

Please sign in to comment.