diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index c2cc06d124c..839b990028e 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -547,6 +547,23 @@ private static void CreateTableMapping( foreach (var complexProperty in mappedType.GetComplexProperties()) { + // For TPT: skip complex properties declared on base entity types that have their own table. + // Those complex properties will be processed when mapping the declaring entity type's own table. + // For TPC, all properties (including inherited) must be included in each concrete table, so skip this check. + if (mappedType is IEntityType mappedEntityType + && mappedEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy + && complexProperty.DeclaringType is IEntityType complexPropertyDeclaringEntityType + && complexPropertyDeclaringEntityType != mappedEntityType) + { + var declaringTableName = complexPropertyDeclaringEntityType.GetTableName(); + if (declaringTableName != null + && (declaringTableName != mappedTable.Name + || complexPropertyDeclaringEntityType.GetSchema() != mappedTable.Schema)) + { + continue; + } + } + var complexType = complexProperty.ComplexType; var complexTableMappings = diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 13c2322687d..e4ec9d5d600 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -3322,6 +3322,27 @@ public void Complex_property_json_column_is_nullable_in_TPH_hierarchy() Assert.IsType(jsonColumn); } + [ConditionalFact] + public void Complex_property_json_column_is_not_duplicated_in_TPT_child_tables() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity() + .UseTptMappingStrategy() + .ComplexProperty(e => e.ComplexProperty, b => b.ToJson()); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + var relationalModel = model.GetRelationalModel(); + + var baseTable = relationalModel.Tables.Single(t => t.Name == nameof(TptBaseEntityWithComplexProperty)); + var childTable = relationalModel.Tables.Single(t => t.Name == nameof(TptDerivedEntityWithoutComplexProperty)); + + // The JSON column for the base complex property must appear only in the base table + Assert.Contains(baseTable.Columns, c => c.Name == nameof(TptBaseEntityWithComplexProperty.ComplexProperty)); + Assert.DoesNotContain(childTable.Columns, c => c.Name == nameof(TptBaseEntityWithComplexProperty.ComplexProperty)); + } + [ConditionalFact] public void Can_use_relational_model_with_functions_and_json_owned_types() { @@ -3497,6 +3518,14 @@ private class TphEntityWithComplexProperty : TphBaseEntity public ComplexData ComplexProperty { get; set; } } + private abstract class TptBaseEntityWithComplexProperty + { + public int Id { get; set; } + public ComplexData ComplexProperty { get; set; } + } + + private class TptDerivedEntityWithoutComplexProperty : TptBaseEntityWithComplexProperty; + [ComplexType] private class ComplexData {