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.51k stars 3.13k forks source link

Linq select cannot project an entity containing both Json columns and ICollections #30266

Closed pella-ias closed 1 year ago

pella-ias commented 1 year ago

Removing either the Json column or the collection from the projection solves the issue.

Minimal code to reproduce the error:

using Microsoft.EntityFrameworkCore;

var options = new DbContextOptionsBuilder<TestDbContext>().UseSqlServer("").Options;
var context = new TestDbContext(options);

var list = context.TestEntities
    .AsNoTracking()
    .Select(t => new TestEntity()
    {
        Id = t.Id,
        JsonColumn = t.JsonColumn,
        ChildEntities = t.ChildEntities,
    }).ToList();

public class TestEntity
{
    public Guid Id { get; set; }
    public JsonColumn JsonColumn { get; set; }
    public ICollection<TestChildEntity> ChildEntities { get; set; }
}

public class JsonColumn { public string Text { get; set; } }
public class TestChildEntity { public Guid Id { get; set; } }
public class TestDbContext : DbContext
{
    public DbSet<TestEntity> TestEntities { get; set; }
    public TestDbContext(DbContextOptions options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<TestEntity>().OwnsOne(e => e.JsonColumn, nav => nav.ToJson());
    }
}

Stack trace:

Unhandled exception. System.InvalidOperationException: variable '' of type 'JsonColumn' referenced from scope '', but it is not defined
   at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)
   at System.Linq.Expressions.Compiler.VariableBinder.VisitParameter(ParameterExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at System.Linq.Expressions.ExpressionVisitor.Visit(ReadOnlyCollection`1 nodes)
   at System.Linq.Expressions.Compiler.VariableBinder.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Compiler.LambdaCompiler.Compile(LambdaExpression lambda)
   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__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Program.<Main>$(String[] args) in \Program.cs:line 6

I've found a possibly related issue on the dotnet runtime https://github.com/dotnet/runtime/issues/56262

EF Core version: 7.0.2 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: net7.0 (sdk 7.0.102) Operating system: Win 10 IDE: VSCode

meigetsu commented 1 year ago

Hello, I am having exactly the same issue on e project, I confirm removing either the collection or the json column works but that's that's not an option in my situation. Is there any known workaround for this (other then not getting one of them)?

ajcvickers commented 1 year ago

@maumar Any workaround here?

maumar commented 1 year ago

problem comes from the bad interaction between json materializer code and the collection result coordinator. Fails for any collection (List, Array etc) not only ICollection interface.

@meigetsu @pella-ias workaround is to project entire entity and use include to bring back the related collection, rather than project the collection directly. Include uses slightly different path which is not affected by the bug.

Something like this:

        var list = ctx.TestEntities
            .AsNoTracking()
            .Include(x => x.ChildEntities)
            .ToList()
            .Select(t => new TestEntity()
            {
                Id = t.Id,
                JsonColumn = t.JsonColumn,
                ChildEntities = t.ChildEntities,
            }).ToList();
maumar commented 1 year ago

@ajcvickers second customer report on this issue - patch candidate or shall we wait for a few more?