Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create table mappings for complex types in TPC #35314

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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;
Comment on lines +2661 to +2662
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roji There can now be more than one mapping per complex type when all types in TPC hierarchy are selected. I think this code needs significant updates to support this. Do you see a simple/safe way to make it work for 9.0.x?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, makes sense... I'd have to look deeper, but is PR as-is sufficient to make it work? If so it doesn't necessarily seem to bad for a patch?

BTW if things do get complicated here and we only fix this for 10 (as part of the big complex type push), I don't think it's the end of the world either...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, makes sense... I'd have to look deeper, but is PR as-is sufficient to make it work? If so it doesn't necessarily seem to bad for a patch?

This PR changes the exception from "no elements" to "more than one element"

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's AdHocComplexTypeQueryTestBase which is a bit more targeted... In any case, hopefully as we refactor our tests this wouldn't have to be ad-hoc any more.

{
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
Loading