Skip to content

Commit

Permalink
Create table mappings for complex types in TPC
Browse files Browse the repository at this point in the history
Fixes #35025
  • Loading branch information
AndriySvyryd committed Dec 11, 2024
1 parent 4b3e12f commit ed18929
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ private static void UniquifyColumnNames(
}

var identifyingMemberInfo = property.PropertyInfo ?? (MemberInfo?)property.FieldInfo;
if ((identifyingMemberInfo != null
if ((!(type is IConventionComplexType)
&& identifyingMemberInfo != null
&& identifyingMemberInfo.IsSameAs(otherProperty.PropertyInfo ?? (MemberInfo?)otherProperty.FieldInfo))
|| (property.IsPrimaryKey() && otherProperty.IsPrimaryKey())
|| (property.IsConcurrencyToken && otherProperty.IsConcurrencyToken)
Expand Down Expand Up @@ -286,7 +287,7 @@ private static void UniquifyColumnNames(
}
}

foreach (var complexProperty in type.GetDeclaredComplexProperties())
foreach (var complexProperty in type.GetComplexProperties())
{
UniquifyColumnNames(complexProperty.ComplexType, columns, storeObject, maxLength);
}
Expand Down
6 changes: 5 additions & 1 deletion src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ private static void CreateTableMapping(
CreateColumnMapping(column, property, tableMapping);
}

foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
foreach (var complexProperty in mappedType.GetComplexProperties())
{
var complexType = complexProperty.ComplexType;

Expand All @@ -539,6 +539,10 @@ private static void CreateTableMapping(
complexTableMappings = [];
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, complexTableMappings);
}
else if (complexTableMappings.Any(m => m.Table == table))
{
continue;
}

CreateTableMapping(
relationalTypeMappingSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2658,8 +2658,10 @@ public static StructuralTypeShaperExpression GenerateComplexPropertyShaperExpres

// We do not support complex type splitting, so we will only ever have a single table/view mapping to it.
// See Issue #32853 and Issue #31248
var complexTypeTable = complexProperty.ComplexType.GetViewOrTableMappings().Single().Table;
if (!containerProjection.TableMap.TryGetValue(complexTypeTable, out var tableAlias))
var complexTypeTable = complexProperty.ComplexType.GetViewOrTableMappings().SingleOrDefault(
ctm => containerProjection.TableMap.ContainsKey(ctm.Table))?.Table;
if (complexTypeTable == null
|| !containerProjection.TableMap.TryGetValue(complexTypeTable, out var tableAlias))
{
complexTypeTable = complexProperty.ComplexType.GetDefaultMappings().Single().Table;
tableAlias = containerProjection.TableMap[complexTypeTable];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,69 @@ public static DateTime Modify(DateTime date)
}

#endregion

#region 35025

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Select_base_with_ComplexTypes_in_TPC(bool async)
{
var contextFactory = await InitializeAsync<Context35025>();
using var context = contextFactory.CreateContext();

var query1 = context.Events;

var count = 0;
if (async)
{
count = (await query1.ToListAsync()).Count;
}
else
{
count = query1.ToList().Count;
}

Assert.Equal(0, count);
}

protected class Context35025(DbContextOptions options) : DbContext(options)
{
public DbSet<EventBase> Events { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EventBase>(builder =>
{
builder.ComplexProperty(e => e.Knowledge).IsRequired();
builder.UseTpcMappingStrategy();
});
modelBuilder.Entity<EventWithIdentification>();
modelBuilder.Entity<RealEvent>();
}

public abstract class EventBase
{
public int Id { get; set; }
public Period Knowledge { get; set; }
}

public class EventWithIdentification : EventBase
{
public long ExtraId { get; set; }
}

public class RealEvent : EventBase
{
}

public class Period
{
public DateTimeOffset From { get; set; }
}

}

#endregion
}
}

Expand Down
34 changes: 27 additions & 7 deletions test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ private static void AssertDefaultMappings(IRelationalModel model, Mapping mappin
Assert.Equal(5, specialCustomerTable.EntityTypeMappings.Count());
Assert.All(specialCustomerTable.EntityTypeMappings, t => Assert.Null(t.IsSharedTablePrincipal));

Assert.Equal(10, specialCustomerTable.Columns.Count());
Assert.Equal(11, specialCustomerTable.Columns.Count());

Assert.True(specialtyColumn.IsNullable);
}
Expand Down Expand Up @@ -794,13 +794,18 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)

var customerPk = specialCustomerType.FindPrimaryKey();

var complexType = abstractBaseType.GetComplexProperties().Single().ComplexType;

if (mapping == Mapping.TPT)
{
var baseTable = abstractBaseType.GetTableMappings().Single().Table;
Assert.Equal("AbstractBase", baseTable.Name);
Assert.Equal(nameof(Customer), customerTable.Name);
Assert.Null(abstractCustomerType.GetTableName());
Assert.Equal(nameof(SpecialCustomer), specialCustomerType.GetTableName());

Assert.Same(baseTable, complexType.GetTableMappings().Single().Table);

Assert.Equal(3, specialCustomerType.GetTableMappings().Count());
Assert.Null(specialCustomerType.GetTableMappings().First().IsSplitEntityTypePrincipal);
Assert.True(specialCustomerType.GetTableMappings().First().IncludesDerivedTypes);
Expand Down Expand Up @@ -956,18 +961,19 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
Assert.Equal(baseTable.Name, abstractCustomerType.GetTableName());
Assert.Equal(baseTable.Name, specialCustomerType.GetTableName());

Assert.Same(baseTable, complexType.GetTableMappings().Single().Table);

Assert.True(specialCustomerTypeMapping.IncludesDerivedTypes);
Assert.Same(customerTable, specialCustomerTable);

Assert.Equal(6, specialCustomerTable.EntityTypeMappings.Count());
Assert.True(specialCustomerTable.EntityTypeMappings.First().IsSharedTablePrincipal);
Assert.False(specialCustomerTable.EntityTypeMappings.Last().IsSharedTablePrincipal);

Assert.Equal(12, specialCustomerTable.Columns.Count());
Assert.Equal(13, specialCustomerTable.Columns.Count());

var addressColumn = specialCustomerTable.Columns.Single(
c =>
c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address));
c => c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address));

Assert.True(specialtyColumn.IsNullable);
Assert.True(addressColumn.IsNullable);
Expand Down Expand Up @@ -1035,14 +1041,17 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
Assert.Null(abstractCustomerType.GetTableName());
Assert.Equal(nameof(SpecialCustomer), specialCustomerType.GetTableName());

Assert.Equal(idProperty.GetTableColumnMappings().Select(m => m.TableMapping.Table).OrderBy(t => t.Name),
complexType.GetTableMappings().Select(m => m.Table).OrderBy(t => t.Name));

Assert.False(specialCustomerTypeMapping.IncludesDerivedTypes);
Assert.NotSame(customerTable, specialCustomerTable);

Assert.Empty(ordersTable.ReferencingForeignKeyConstraints);
Assert.Empty(customerTable.ReferencingForeignKeyConstraints);

Assert.Null(customerTable.EntityTypeMappings.Single().IsSharedTablePrincipal);
Assert.Equal(5, customerTable.Columns.Count());
Assert.Equal(6, customerTable.Columns.Count());

Assert.Single(specialCustomerTable.EntityTypeMappings);

Expand All @@ -1055,8 +1064,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
Assert.Equal(2, extraSpecialCustomerTable.EntityTypeMappings.Count());

var addressColumn = extraSpecialCustomerTable.Columns.Single(
c =>
c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address));
c => c.Name == nameof(SpecialCustomer.Details) + "_" + nameof(CustomerDetails.Address));
Assert.False(addressColumn.IsNullable);

var abstractStringProperty = abstractStringColumn.PropertyMappings.Single().Property;
Expand Down Expand Up @@ -2053,6 +2061,8 @@ private IRelationalModel CreateTestModel(
cb.UseTptMappingStrategy();
}

cb.ComplexProperty(c => c.Tag).IsRequired();

// TODO: Don't map it on the base #19811
cb.Property<string>("SpecialtyAk");
});
Expand Down Expand Up @@ -2468,6 +2478,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o

modelBuilder.Ignore<AbstractCustomer>();
modelBuilder.Ignore<Customer>();
modelBuilder.Ignore<Tag>(); //#31248

modelBuilder.Entity<SpecialCustomer>(
cb =>
Expand Down Expand Up @@ -2657,6 +2668,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o

modelBuilder.Ignore<AbstractCustomer>();
modelBuilder.Ignore<Customer>();
modelBuilder.Ignore<Tag>(); //#31248

modelBuilder.Entity<SpecialCustomer>(
cb =>
Expand Down Expand Up @@ -2777,6 +2789,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o

modelBuilder.Ignore<AbstractCustomer>();
modelBuilder.Ignore<Customer>();
modelBuilder.Ignore<Tag>(); //#31248

modelBuilder.Entity<SpecialCustomer>(
cb =>
Expand Down Expand Up @@ -2907,6 +2920,7 @@ public void Can_use_relational_model_with_entity_splitting_and_table_splitting_o
public void Can_use_relational_model_with_keyless_TPH()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Ignore<Tag>();

modelBuilder.Entity<Customer>(
cb =>
Expand Down Expand Up @@ -3226,6 +3240,12 @@ private enum MyEnum : ulong
private abstract class AbstractBase
{
public int Id { get; set; }
public Tag Tag { get; set; }
}

public class Tag
{
public string Name { get; set; }
}

private class Customer : AbstractBase
Expand Down
2 changes: 1 addition & 1 deletion test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4365,7 +4365,7 @@ public virtual void Lazy_loading_finds_correct_entity_type_with_multiple_queries
}

[ConditionalFact]
public virtual void Lazy_loading_shares_service__property_on_derived_types()
public virtual void Lazy_loading_shares_service_property_on_derived_types()
{
using var context = CreateContext(lazyLoadingEnabled: true);
var parson = context.Set<Parson>().Single();
Expand Down
Loading

0 comments on commit ed18929

Please sign in to comment.