Skip to content

EfCore 8: Primitive collections in JSON documents error with init #32310

@Jejuni

Description

@Jejuni

Problem

When using init for properties that are set up as a primitive collection saving them to the database works, but trying to materialize them leads to a System.ArgumentException: 'Expression must be writeable (Parameter 'left')'.

This happens with both MsSql as well as sqlite providers (probably all others as well, but have only tested those 2).

This does NOT happen when using a "plain" primitive collection as a top level property of an entity. init is fine here.

Code and Repro

You can find a runnable sample showing the problem here: https://github.com/Jejuni/PrimitiveCollectionInJsonInitProblem

The code is adapted from the samples referenced in the documentation: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/whatsnew#primitive-collections-in-json-documents

These are the entities and DbContext.
Note the only relevant change from the official sample is the change from set to init on the primitive collection DaysVisited.

public class Pub
{
    public Pub(string name)
    {
        Name = name;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public Visits Visits { get; set; } = null!;
}

public class Visits
{
    public string? LocationTag { get; set; }
    public List<DateOnly> DaysVisited { get; init; } = null!; // Change this from `init` to `set` and it works
}

internal class TestContext : DbContext
{
    public static bool UseSqlite { get; set; }
    public DbSet<Pub> Pubs => Set<Pub>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (UseSqlite)
            optionsBuilder.UseSqlite("DataSource=sqlite.db");
        else
            optionsBuilder.UseSqlServer(
                "Server=localhost,5433;Initial Catalog=PrimitiveCollectionsProblem;User Id=sa;Password=Pass@word;TrustServerCertificate=true");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Pub>(
            b => { b.OwnsOne(e => e.Visits).ToJson(); });
    }
}

The code in Program.cs shows the problem:

TestContext.UseSqlite = false;

await using (var ctx = new TestContext())
{
    await ctx.Database.EnsureDeletedAsync();
    await ctx.Database.EnsureCreatedAsync();
}

await using (var ctx = new TestContext())
{
    var user = new Pub("MyPub")
    {
        Visits = new Visits
        {
            LocationTag = "tag",
            DaysVisited = new List<DateOnly> { new(2023, 1, 1) }
        }
    };

    ctx.Pubs.Add(user);
    await ctx.SaveChangesAsync();
}

await using (var ctx = new TestContext())
{
    var pubs = await ctx.Pubs.ToListAsync(); // Exception thrown here
}

Exception Stack Trace

System.ArgumentException
  HResult=0x80070057
  Message=Expression must be writeable (Parameter 'left')
  Source=System.Linq.Expressions
  StackTrace:
   at System.Linq.Expressions.Expression.RequiresCanWrite(Expression expression, String paramName)
   at System.Linq.Expressions.Expression.Assign(Expression left, Expression right)
   at System.Linq.Expressions.Expression.MakeBinary(ExpressionType binaryType, Expression left, Expression right, Boolean liftToNull, MethodInfo method, LambdaExpression conversion)
   at System.Linq.Expressions.Expression.MakeBinary(ExpressionType binaryType, Expression left, Expression right)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonEntityMaterializerRewriter.ValueBufferTryReadValueMethodsReplacer.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonEntityMaterializerRewriter.VisitSwitch(SwitchExpression switchExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonEntityMaterializerRewriter.VisitConditional(ConditionalExpression conditionalExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitConditional(ConditionalExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonEntityMaterializerRewriter.VisitConditional(ConditionalExpression conditionalExpression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitBlockExpressions(ExpressionVisitor visitor, BlockExpression block)
   at System.Linq.Expressions.ExpressionVisitor.VisitBlock(BlockExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.JsonEntityMaterializerRewriter.Rewrite(BlockExpression jsonEntityShaperMaterializer)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateJsonShapers(IEntityType entityType, Boolean nullable, ParameterExpression jsonReaderDataParameter, ParameterExpression keyValuesParameter, Expression parentEntityExpression, INavigation navigation)
   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__DisplayClass12_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.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Collections.Generic.IAsyncEnumerable<TEntity>.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__67`1.MoveNext()
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\Path\PrimitiveCollectionInJsonInitProblem\PrimitiveCollectionInJsonInitProblem\Program.cs:line 29
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\Path\PrimitiveCollectionInJsonInitProblem\PrimitiveCollectionInJsonInitProblem\Program.cs:line 33

Include provider and version information

EF Core version: 8.0
Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer / Sqlite)
Target framework: (e.g. .NET 8.0)
Operating system:
IDE: (e.g. Visual Studio 2022 17.8)

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions