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.79k stars 3.19k forks source link

Regression when using ImmutableArray's Contains, while Array's Contains method still works. #35102

Open fschwiet opened 1 day ago

fschwiet commented 1 day ago

File a bug

I ran into a regression going from 8.0.1 to 9.0.0 using the Sqlite provider. I have some code which is checking if the string value of a column (column name Value) is one of a number of values (using dbKeys.Contains). dbKeys was an ImmutableArray, I found if I used an Array instead it start to work. But I don't see any reason it shouldn't keep working with an ImmutableArray.

Include your code

The following code using ToImmutableArray stopped working when going from 8.0.1 to 9.0.0:

        var dbKeys = dbTags.Select(t => t.Value).Distinct().ToImmutableArray();

        var existingEntities = await context
            .Tags.Where(t => dbKeys.Contains(t.Value))
            .ToArrayAsync(cancellationToken);

But it works if I use ToArray():

        var dbKeys = dbTags.Select(t => t.Value).Distinct().ToArray();

        var existingEntities = await context
            .Tags.Where(t => dbKeys.Contains(t.Value))
            .ToArrayAsync(cancellationToken);

Include stack traces

Unhandled exception. System.ArgumentException: Expression of type 'System.Collections.Immutable.ImmutableArray`1[System.String]' cannot be used for parameter of type 'System.Collecti
ons.Generic.IEnumerable`1[System.String]' of method 'System.Linq.IQueryable`1[System.String] AsQueryable[String](System.Collections.Generic.IEnumerable`1[System.String])' (Parameter 'arg0')
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.TryConvertCollectionContainsToQueryableContains(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.Normalize(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   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.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Include provider and version information

EF Core version: regressed with version 9.0.0, worked for version 8.0.1 Database provider: Microsoft.EntityFrameworkCore.Sqlite regressed with version 9.0.0, worked for version 8.0.1 Target framework: regression seen with net9.0, worked with net8.0 Operating system: Windows 11 Home IDE: JetBrains Rider 2024.2.5

roji commented 19 hours ago

Confirmed regression from 8.0 to 9.0; not specific to SQLite.

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

ImmutableArray<string> tags = ["foo", "bar"];
// List<string> tags = ["foo", "bar"]; // This works

var existingEntities = await context
    .Tags.Where(t => tags.Contains(t.Value))
    .ToArrayAsync();

public class BlogContext : DbContext
{
    public DbSet<Tag> Tags { 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 Tag
{
    public int Id { get; set; }
    public string Value { get; set; }
}

@cincuranet assigning to you as you worked in this area in 9.0, IIRC.