Skip to content

AwkEng/EfCoreComplexPropertyBug

Repository files navigation

Regression: LINQ Select with struct ComplexProperty + collection navigation throws InvalidOperationException

Bug description

Upgrading from EF Core 11.0.0-preview.1 to 11.0.0-preview.2 introduces a regression when a LINQ Select projection combines:

  1. A collection navigation whose elements have a readonly record struct ComplexProperty projected via constructor
  2. A struct ComplexProperty with nested struct ComplexProperties projected via constructor on the same entity

Either projection works individually. When combined in the same Select, EF Core throws:

InvalidOperationException: An expression of type 'Outer' cannot be used to initialize an array of type 'System.Object'

The stack trace points to ShaperProcessingExpressionVisitor.ProcessProjection calling LiftComplexTypeToNewArray, which appears new in preview.2 and does not box the struct before adding it to the object[] array initializer expression.

Reproduces on both SQLite and PostgreSQL — this is an EF Core core issue, not provider-specific.

Your code

#:package Microsoft.EntityFrameworkCore@11.0.0-preview.2.26159.112
#:package Microsoft.EntityFrameworkCore.Sqlite@11.0.0-preview.2.26159.112

using Microsoft.EntityFrameworkCore;

var options = new DbContextOptionsBuilder<AppDbContext>()
    .UseSqlite("Data Source=:memory:")
    .Options;

using var db = new AppDbContext(options);
db.Database.OpenConnection();
db.Database.EnsureCreated();

var parentId = Guid.NewGuid();
db.Parents.Add(new Parent
{
    Id = parentId,
    Name = "P1",
    Coords = new Outer
    {
        First = new Inner { A = "1", B = "2" },
        Second = new Inner { A = "3", B = "4" }
    },
    Children = [new Child { Id = Guid.NewGuid(), ParentId = parentId, Data = new Inner { A = "x", B = "y" } }]
});
db.SaveChanges();

// Works in preview.1, throws in preview.2
var result = await db.Parents
    .AsNoTracking()
    .Where(p => p.Id == parentId)
    .Select(p => new ParentVm(
        p.Name,
        p.Children.Select(c => new ChildVm(new InnerVm(c.Data))).ToList(),  // collection nav + struct
        new OuterVm(p.Coords)                                               // nested struct
    ))
    .FirstOrDefaultAsync();

Console.WriteLine(result);

// ── Domain ──────────────────────────────────────────────────────────

public readonly record struct Inner
{
    public string? A { get; init; }
    public string? B { get; init; }
}

public readonly record struct Outer
{
    public Inner First { get; init; }
    public Inner Second { get; init; }
}

public class Child
{
    public Guid Id { get; set; }
    public Guid ParentId { get; set; }
    public Inner Data { get; set; }
    public Parent? Parent { get; set; }
}

public class Parent
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public Outer Coords { get; set; }
    public List<Child> Children { get; set; } = [];
}

// ── View models ─────────────────────────────────────────────────────

public record InnerVm(string? A, string? B)
{
    public InnerVm(Inner i) : this(i.A, i.B) { }
}

public record OuterVm(InnerVm First, InnerVm Second)
{
    public OuterVm(Outer o) : this(new InnerVm(o.First), new InnerVm(o.Second)) { }
}

public record ChildVm(InnerVm Data);
public record ParentVm(string Name, List<ChildVm> Children, OuterVm Coords);

// ── DbContext ───────────────────────────────────────────────────────

public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Parent> Parents => Set<Parent>();
    public DbSet<Child> Children => Set<Child>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Parent>(e =>
        {
            e.HasKey(x => x.Id);
            e.ComplexProperty(x => x.Coords, c =>
            {
                c.ComplexProperty(x => x.First, f =>
                {
                    f.Property(x => x.A).HasColumnName("first_a");
                    f.Property(x => x.B).HasColumnName("first_b");
                });
                c.ComplexProperty(x => x.Second, s =>
                {
                    s.Property(x => x.A).HasColumnName("second_a");
                    s.Property(x => x.B).HasColumnName("second_b");
                });
            });
            e.HasMany(x => x.Children).WithOne(x => x.Parent).HasForeignKey(x => x.ParentId);
        });

        modelBuilder.Entity<Child>(e =>
        {
            e.HasKey(x => x.Id);
            e.ComplexProperty(x => x.Data, d =>
            {
                d.Property(x => x.A).HasColumnName("data_a");
                d.Property(x => x.B).HasColumnName("data_b");
            });
        });
    }
}

Expected behavior

Query completes and returns ParentVm { Name = P1, Children = [...], Coords = OuterVm { ... } } (as it does on preview.1).

Actual behavior

Throws InvalidOperationException during query compilation.

Stack traces

System.InvalidOperationException: An expression of type 'Outer' cannot be used to initialize an array of type 'System.Object'
   at System.Linq.Expressions.Expression.NewArrayInit(Type type, IEnumerable`1 initializers)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.<ProcessProjection>g__LiftComplexTypeToNewArray|19_1(Expression expression, Type newArrayType)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessProjection(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessProjection(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, RelationalCommandCache& relationalCommandCache, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   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.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   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.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Verbose output

No response

EF Core version

11.0.0-preview.2.26159.112 (regression from 11.0.0-preview.1.26104.118)

Database provider

Reproduces on both:

  • Microsoft.EntityFrameworkCore.Sqlite 11.0.0-preview.2
  • Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-preview.2

Target framework

.NET 11.0

Operating system

Windows 11

IDE

Visual Studio 2022

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages