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

ExecuteUpdateAsync breaks with Many-To-Many and AutoInclude #35523

Open
mvarendorff2 opened this issue Jan 24, 2025 · 1 comment
Open

ExecuteUpdateAsync breaks with Many-To-Many and AutoInclude #35523

mvarendorff2 opened this issue Jan 24, 2025 · 1 comment

Comments

@mvarendorff2
Copy link

mvarendorff2 commented Jan 24, 2025

Bug description

Configuring an entity to AutoInclude a many-to-many relationship renders ExecuteUpdateAsync dysfunctional.

I found #30250 which is closed as duplicate to a resolved issue almost 2 years ago however it's reproducible in the most recent stable version. It seems the bug is only reproducible with many-to-many relationships at this point (the linked issue did not seem to cover that scenario).

Your code

<!-- Playground.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
  </ItemGroup>

</Project>
// Program.cs

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddDbContext<BrokenContext>(options => options.UseSqlite("Data Source=:memory:"));

var app = builder.Build();

await using var scope = app.Services.CreateAsyncScope();
var brokenContext = scope.ServiceProvider.GetRequiredService<BrokenContext>();
await brokenContext.Database.MigrateAsync();
await brokenContext.BrokenModels.ExecuteUpdateAsync(calls => calls.SetProperty(m => m.Property, ""));

public class BrokenModel {
    public Guid Id { get; set; }
    public string Property { get; set; } = "";
    public IEnumerable<BrokenRelationship> Relationships { get; set; }
}

public class BrokenRelationship {
    public Guid Id { get; set; }
    public IEnumerable<BrokenModel> BrokenModels { get; set; }
}

public class BrokenContext : DbContext{
    public DbSet<BrokenModel> BrokenModels { get; set; } = null!;
    public DbSet<BrokenRelationship> BrokenRelationships { get; set; } = null!;

    public BrokenContext(DbContextOptions<BrokenContext> options): base(options) {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<BrokenModel>().Navigation(m => m.Relationships).AutoInclude();
    }
}

Stack traces

Unhandled exception. System.InvalidOperationException: The LINQ expression 'DbSet<BrokenModel>()
    .Select(b0 => Include(
        Entity: b0,
        Navigation: Relationships, MaterializeCollectionNavigation(
            Navigation: BrokenModel.Relationships,
            Subquery: DbSet<Dictionary<string, object>>("BrokenModelBrokenRelationship")
                .Where(b1 => EF.Property<Guid?>(b0, "Id") != null && object.Equals(
                    objA: (object)EF.Property<Guid?>(b0, "Id"),
                    objB: (object)EF.Property<Guid?>(b1, "BrokenModelsId")))
                .Join(
                    inner: DbSet<BrokenRelationship>(),
                    outerKeySelector: b1 => (object)EF.Property<Guid?>(b1, "RelationshipsId"),
                    innerKeySelector: b2 => (object)EF.Property<Guid?>(b2, "Id"),
                    resultSelector: (b1, b2) => new TransparentIdentifier<Dictionary<string, object>, BrokenRelationship>(
                        Outer = b1,
                        Inner = b2
                    ))
                .Select(ti => NavigationExpandingExpressionVisitor.FetchJoinEntity<Dictionary<string, object>, BrokenRelationship>(
                    joinEntity: ti.Outer,
                    targetEntity: ti.Inner))))
    .ExecuteUpdate(calls => calls.SetProperty<string>(
        propertyExpression: m => m.Property,
        valueExpression: ""))' could not be translated. Additional information: The following lambda argument to 'SetProperty' does not represent a valid property to be set: 'm => m.Property'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   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.<ExecuteCore>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   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.ExecuteUpdateAsync[TSource](IQueryable`1 source, Expression`1 setPropertyCalls, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in D:\playground\Program.cs:line 13
   at Program.<Main>$(String[] args) in D:\playground\Program.cs:line 13
   at Program.<Main>(String[] args)

EF Core version

9.0.1

Database provider

Microsoft.EntityFrameworkCore.Sqlite

Target framework

.NET 9.0

Operating system

Windows 11

IDE

Rider 2024.3.3

@roji
Copy link
Member

roji commented Jan 27, 2025

Confirmed, putting as a bug on the backlog for now - we have some other known issues with AutoInclude and bulk update.

Slightly simplified repro
await using var context = new BrokenContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

await context.BrokenModels.ExecuteUpdateAsync(calls => calls.SetProperty(m => m.Property, ""));

public class BrokenContext : DbContext
{
    public DbSet<BrokenModel> BrokenModels { get; set; } = null!;
    public DbSet<BrokenRelationship> BrokenRelationships { get; set; } = null!;

    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();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<BrokenModel>().Navigation(m => m.Relationships).AutoInclude();
    }
}

public class BrokenModel
{
    public Guid Id { get; set; }
    public string Property { get; set; } = "";
    public IEnumerable<BrokenRelationship> Relationships { get; set; }
}

public class BrokenRelationship
{
    public Guid Id { get; set; }

    // Many-to-one works:
    // public BrokenModel BrokenModels { get; set; }

    public IEnumerable<BrokenModel> BrokenModels { get; set; }
}

@roji roji added the type-bug label Jan 27, 2025
@roji roji added this to the Backlog milestone Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants