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

Array.Concat stopped working in EFCore8 #3001

Closed
srasch opened this issue Nov 30, 2023 · 3 comments · Fixed by #3005
Closed

Array.Concat stopped working in EFCore8 #3001

srasch opened this issue Nov 30, 2023 · 3 comments · Fixed by #3005
Assignees
Labels
bug Something isn't working
Milestone

Comments

@srasch
Copy link

srasch commented Nov 30, 2023

I try to update some rows in the database using this Updatemethod:

await db.Test
    .Where(x => x.RoleId == 2)
    .ExecuteUpdateAsync(c => c.SetProperty(x => x.TextParts, x => x.TextParts.Concat(arr)));

or

  await db.Test
    .Where(x => x.RoleId == 1)
    .ExecuteUpdateAsync(c => c.SetProperty(x => x.TextParts, x => x.TextParts.Append("Foo")));

Both are working using EFCore7 and none are working in EFCore8
The error message for Concat is

InvalidOperationException: The LINQ expression 'DbSet<TestModel>()
    .Where(t => t.RoleId == 2)
    .ExecuteUpdate(c => c.SetProperty<IEnumerable<string>>(
        propertyExpression: x => x.TextParts, 
        valueExpression: x => x.TextParts
            .Concat(__arr_0)))' could not be translated. Additional information: Translation of method 'System.Linq.Enumerable.Concat' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information.
The following lambda argument to 'SetProperty' does not represent a valid value: 'x => x.TextParts
    .Concat(__arr_0)'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

and the message for Append:

InvalidOperationException: The LINQ expression 'DbSet<TestModel>()
    .Where(t => t.RoleId == 1)
    .ExecuteUpdate(c => c.SetProperty<IEnumerable<string>>(
        propertyExpression: x => x.TextParts, 
        valueExpression: x => x.TextParts
            .Append("Foo")))' could not be translated. Additional information: Translation of method 'System.Linq.Enumerable.Append' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information.
The following lambda argument to 'SetProperty' does not represent a valid value: 'x => x.TextParts
    .Append("Foo")'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Here is a complete LinqPad-Script:

async Task Main()
{
  var db = new TestContext();

//  await db.Database.EnsureDeletedAsync();
  await db.Database.EnsureCreatedAsync();

  await db.Database.MigrateAsync();

  if (!await db.Test.AnyAsync())
  {
    await db.Test.AddRangeAsync(
      new TestModel { ProjectId = 1, RoleId = 1, },
      new TestModel { ProjectId = 1, RoleId = 2, },
      new TestModel { ProjectId = 2, RoleId = 1, },
      new TestModel { ProjectId = 2, RoleId = 2, TextParts = ["Hello"], },
      new TestModel { ProjectId = 3, }
    );
    await db.SaveChangesAsync();
  }
  
  string[] arr = [ "World" ];
  
  await db.Test
    .Where(x => x.RoleId == 2)
    .ExecuteUpdateAsync(c => c.SetProperty(x => x.TextParts, x => x.TextParts.Concat(arr)));

  await db.Test
    .Where(x => x.RoleId == 1)
    .ExecuteUpdateAsync(c => c.SetProperty(x => x.TextParts, x => x.TextParts.Append("Foo")));
}

public class TestModel
{
  public Guid Id { get; set; }
  public int ProjectId { get; set; }
  public int RoleId { get; set; }
  public string[] TextParts { get; set; }
}

public class TestContext : DbContext
{
  public DbSet<TestModel> Test { get; set; }

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

    modelBuilder.Entity<TestModel>()
      .HasIndex(x => x.Id);
  }
  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    optionsBuilder
      .UseNpgsql("<DBCONNECTION>", o => o.SetPostgresVersion(16, 0))
      .LogTo(x => Console.WriteLine(x), new[] { new EventId(20101)});
    base.OnConfiguring(optionsBuilder);
  }
}

but not for EFCore8

the tested Nuget-Packages are:

Package working not working
Microsoft.EntityFrameworkCore 7.0.14 8.0.0
Microsoft.EntityFrameworkCore.Abstractions 7.0.14 8.0.0
Microsoft.EntityFrameworkCore.Relational 7.0.14 8.0.0
Newtonsoft.Json 13.0.3 13.0.3
Npgsql 7.0.6 8.0.0
Npgsql.EntityFrameworkCore.EFCore 7.0.11 8.0.0
@roji
Copy link
Member

roji commented Nov 30, 2023

Thanks for filing this. It may take me a while before I'm able to investigate, but I'll definitely be looking into it.

@roji
Copy link
Member

roji commented Dec 4, 2023

This turned out to be a bit of a rabbit hole... EF 8.0 got 1st-class support for queryable (primitive) collections, and so operators like Concat/Append are now handled via that "pipeline" (rather than as arbitrary method translations). However, there are various issues specifically with using queryable collections inside ExecuteUpdate - see dotnet/efcore#32494.

In the meantime I've submitted #3005 to bring back enumerable translations for Concat/Append specifically, for 8.0.1.

@srasch
Copy link
Author

srasch commented Dec 4, 2023

thank you so much for your fast investigation. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants