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

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

Closed
Jejuni opened this issue Nov 15, 2023 · 0 comments · Fixed by #32342
Closed

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

Jejuni opened this issue Nov 15, 2023 · 0 comments · Fixed by #32342
Labels
area-json closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Milestone

Comments

@Jejuni
Copy link

Jejuni commented Nov 15, 2023

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)

@ajcvickers ajcvickers added this to the 8.0.x milestone Nov 17, 2023
@ajcvickers ajcvickers self-assigned this Nov 17, 2023
ajcvickers added a commit that referenced this issue Nov 18, 2023
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Nov 18, 2023
@ajcvickers ajcvickers reopened this Nov 20, 2023
@ajcvickers ajcvickers modified the milestones: 8.0.x, 8.0.1 Nov 20, 2023
@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-json closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants