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

InvalidOperationException when calling ExecuteUpdate with owned entity & using current value #30528

Closed mgrosperrin closed 1 year ago

mgrosperrin commented 1 year ago

When calling ExecuteUpdate to conditionnaly update an entity with owned subentity, an System.InvalidOperationException is thrown.

Repro code here: https://github.com/mgrosperrin/repro_issues/tree/efcore-executeupdate-owned-entity My goal is to update the property only if the user provide a value and to use the current value otherwise (to response to a PATCH API call).

The code in the repo use the null coalescing operator, but I have the same exception with the following code: updates.SetProperty(e => e.Name, e => e.Name)

Include stack traces

Include the full exception message and stack trace for any exception you encounter.

Use triple-tick fences for stack traces. For example:

System.InvalidOperationException: 'The LINQ expression 'DbSet<Entity>()
    .Select(e => IncludeExpression(
        EntityExpression:
        e, 
        NavigationExpression:
        EF.Property<SubEntity>(e, "Owned"), Owned)
    )
    .ExecuteUpdate(updates => updates.SetProperty<string>(
        propertyExpression: e => e.Name, 
        valueExpression: e => __newName_0 ?? e.Name).SetProperty<string>(
        propertyExpression: e => e.DisplayName, 
        valueExpression: e => __newDisplayName_1 ?? e.DisplayName))' could not be translated. Additional information: The following 'SetProperty' failed to translate: 'SetProperty(e => e.DisplayName, e => __newDisplayName_1 ?? e.DisplayName)'.  See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

Include provider and version information

EF Core version: 7.0.4 Database provider: Microsoft.EntityFrameworkCore.SqlServer Target framework: .NET 7.0 Operating system: Windows 11 IDE: Visual Studio 2022 17.5.2

roji commented 1 year ago

Minimal repro with 7.0.4:

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

ctx.Entities.ExecuteUpdate(updates => updates.SetProperty(e => e.Name, e => e.Name));

class BlogContext : DbContext
{
    public DbSet<Blog> Entities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(@"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public BlogDetails Owned { get; set; }
}

[Owned]
public class BlogDetails
{
    public string Value { get; set; }
}

Exception:

Unhandled exception. System.InvalidOperationException: The LINQ expression 'DbSet<Blog>()
    .Select(b => IncludeExpression(
        EntityExpression:
        b, 
        NavigationExpression:
        EF.Property<BlogDetails>(b, "Owned"), Owned)
    )
    .ExecuteUpdate(updates => updates.SetProperty<string>(
        propertyExpression: e => e.Name, 
        valueExpression: e => e.Name))' could not be translated. Additional information: The following 'SetProperty' failed to translate: 'SetProperty(e => e.Name, e => e.Name)'.  See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   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.RelationalQueryableExtensions.ExecuteUpdate[TSource](IQueryable`1 source, Expression`1 setPropertyCalls)
   at Program.<Main>$(String[] args) in /home/roji/projects/test/Program.cs:line 13
   at Program.<Main>$(String[] args) in /home/roji/projects/test/Program.cs:line 13
   at Program.<Main>(String[] args)
ajcvickers commented 1 year ago

@roji

This works:

await context.Entities.ExecuteUpdateAsync(x => x.SetProperty(e => e.Name, "X"));

And this works:

var newVal = "X";
await context.Entities.ExecuteUpdateAsync(x => x.SetProperty(e => e.Name, newVal));

But this throws:

await context.Entities.ExecuteUpdateAsync(x => x.SetProperty(e => e.Name, e => e.Name));
roji commented 1 year ago

@ajcvickers thanks, I'll look into this soon. I may have a hunch.

roji commented 1 year ago

Submitted #30571, definitely a good candidate for patching.