dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.78k stars 3.19k forks source link

ComplexType properties disappear when using TPC mapping #35025

Open dvdwouwe opened 1 week ago

dvdwouwe commented 1 week ago

Include your code

The combination of inheritance mapping and the use of Complex types, gives strange behavior in TPC mapping. I tried TPH and TPT (line 118), and there everything works as expected.

What I see is:

See https://github.com/dvdwouwe/danny-playground-ef/tree/main for reproducing the behavior we see. Commit SHA: d194a6f9a7a783a5579a03ed9ceb764955e0dd2d

Include provider and version information

EF Core version: 8.0.10 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 8.0.403 Operating system: Windows 11 IDE: Rider

cincuranet commented 1 week ago

Not Microsoft.EntityFrameworkCore.SqlServer specific, repros on Microsoft.EntityFrameworkCore.Sqlite as well. Repros on 9.0.0-rc.2.24474.1.

Smaller repro ```csharp using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; await using var context = new MyDbContext(); await context.Database.EnsureDeletedAsync(); await context.Database.EnsureCreatedAsync(); await context .Set() .Where(e => e.Knowledge.To == null) .ToListAsync(); public abstract class EventBase { protected EventBase(int id) { Id = id; } public int Id { get; set; } public Period Knowledge { get; set; } } public class RealEvent : EventBase { public RealEvent(int id) : base(id) { } } public class Period { public DateTimeOffset From { get; set; } public DateTimeOffset? To { get; set; } } public class MyDbContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .EnableDetailedErrors() .EnableSensitiveDataLogging() .UseSqlite() .LogTo(Console.WriteLine, LogLevel.Information); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity(builder => { builder.ComplexProperty(e => e.Knowledge); builder.UseTpcMappingStrategy(); }); modelBuilder.Entity(); } } ```

The table created is only:

      CREATE TABLE "RealEvent" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_RealEvent" PRIMARY KEY
      );

Also, query throws exception:

   at System.Linq.ThrowHelper.ThrowNoElementsException()
   at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
   at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.GenerateComplexPropertyShaperExpression(StructuralTypeProjectionExpression containerProjection, IComplexProperty complexProperty)
   at Microsoft.EntityFrameworkCore.Query.StructuralTypeProjectionExpression.BindComplexProperty(IComplexProperty complexProperty)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.BindComplexProperty(StructuralTypeReferenceExpression typeReference, IComplexProperty complexProperty)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TryBindMember(Expression source, MemberIdentity member, Expression& expression, IPropertyBase& property)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TryBindMember(Expression source, MemberIdentity member, Expression& expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMember(MemberExpression memberExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMember(MemberExpression memberExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at Microsoft.EntityFrameworkCore.Sqlite.Query.Internal.SqliteSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateExpression(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateLambdaExpression(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass11_0`1.<ExecuteCore>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
maumar commented 5 days ago

When trying to bind complex property we are looking for table mapping so that we know which table/columns to bind. However the table mapping is missing here - We build those as part of RelationalModel.Create. Here, the complex type is defined on the abstract base type, and we skip those when creating table mappings. When processing the derived type we only look at declared complex types and so we miss the type defined on the base. As a result the complex property ends up with no table mapping