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

Query could not be translated when using a static ICollection/IList field #35024

Open
TimLange31 opened this issue Oct 31, 2024 · 4 comments · May be fixed by #35029
Open

Query could not be translated when using a static ICollection/IList field #35024

TimLange31 opened this issue Oct 31, 2024 · 4 comments · May be fixed by #35029
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@TimLange31
Copy link

TimLange31 commented Oct 31, 2024

  • Efcore seems to be unable to translate a static readonly ICollection<T> & static readonly IList<T> field into a query when using it inside of a .Contains() When removing the static keyword it seems to be able to execute the query.
  • When using IEnumerable<T> This is not an issue, is this because of the different .Contains() that's being used?
  • This is only happening when the List/Collection is static.
  • If the collection is a static Property instead of a static field this will not throw this exception.
  • Using the private static field as a local variable before using it inside of the query will not throw this exception.
  • Using an .Any(x => x.Equals(...) also does not throw this exception.

Code example:

public class Item
{
    public string Bar { get; set; }
    public string Foo { get; set; }
}

In my class I have the following field (This can be an static readonly IList<T> or static readonly ICollection<T>:

private static readonly IList<string> AllowedFooCodes = new List<string>() { "ABC", "CED" };

My Repository class looks something like this:

public class SampleDataRepository : ISampleDataRepository
{
    private readonly ILogger<SampleDataRepository> _logger;
    private readonly SampleDataStore _sampleDataStore;

    // Can be IList or ICollection, exception will be thrown regardless.
    private static readonly IList<string> AllowedFooCodes = new List<string>() { "ABC", "CED" };
    
    public SampleDataRepository(SampleDataStore store, ILogger<SampleDataRepository> logger)
    {
        _sampleDataStore = store;
        _logger = logger;
    }
    
    public async Task<IEnumerable<Item>> GetItems()
    {
        _sampleDataStore.Items
                        .AsNoTracking()
                        .Where(item => AllowedFooCodes.Contains(item.foo))
                        .ToListAsync();
    }
 
}

StackTrace:

The LINQ expression 'DbSet<Item>()
	.Where(d => (IList<string>)List<string> { "ABC", "CED" }
        .Contains(item.foo) could not be translated. Additional information: Translation of method 'System.Linq.Enumerable.Contains' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   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 System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator() in System.Runtime.CompilerServices\ConfiguredCancelableAsyncEnumerable.cs:line 61
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__67`1.MoveNext()

EF Core version: 8.0.3.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system: 10.0.19045.0
IDE: Visual Studio 2022 17.9.6

@mevelop
Copy link

mevelop commented Oct 31, 2024

Hi, everyone!

I have the similar problem. I have created a static extension method for small cast

public static bool HasItem<TItem>(this IEnumerable<TItem> enumerable, TItem value)
        => (enumerable as ICollection<TItem>)!.Contains(value);

There is code

private async Task<IReadOnlyList<TEntity>> GetCollectionAsync(IReadOnlyList<TId> ids, CancellationToken cancellationToken)
{
    return (await DbSet
            .Where(x => ids.HasItem(x.Id))
            .ToListAsync(cancellationToken))
        .AsReadOnly();
}

And there is the problem

System.InvalidOperationException: The LINQ expression 'DbSet<Client>()
  .Where(c => !(c.DateDeleted.HasValue))
  .Where(c => __ids_0
      .HasItem(c.Id))' could not be translated. Additional information: Translation of method 'System.Linq.EnumerableHelper.HasItem' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
 at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0&)

But that's code is OK

private async Task<IReadOnlyList<TEntity>> GetCollectionAsync(IReadOnlyList<TId> ids, CancellationToken cancellationToken)
{
    return (await DbSet
            .Where(x => (ids as ICollection<TId>)!.Contains(x.Id))
            .ToListAsync(cancellationToken))
        .AsReadOnly();
}

I think that It's something similar, because the problem in (IList<string>)List<string> { "ABC", "CED" }

EF Core 7.0.20, .NET 7 :)

@maumar
Copy link
Contributor

maumar commented Nov 1, 2024

not a regression - also fails in EF7

@maumar
Copy link
Contributor

maumar commented Nov 1, 2024

QueryRootProcessor should convert the the collection into inline query root, but it doesn't match the expression shape properly because there is a convert node around it. This is the expression:

DbSet<Item>()
    .Where(item => (IList<string>)List<string> { "ABC", "CED" }
        .AsQueryable()
        .Contains(item.Foo))

@maumar
Copy link
Contributor

maumar commented Nov 1, 2024

when list is non-static we evaluate it as parameter typed as IList<string> instead of a constant, so no convert node is added

maumar added a commit that referenced this issue Nov 1, 2024
…llection/IList field

Problem was that when converting primitive collection to inline query root we were not matching the expression if it was constant wrapped in convert.
Fix is to remove convert for the purpose of pattern match for the transformation.

Fixes #35024
@maumar maumar added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Nov 1, 2024
@maumar maumar added this to the 10.0.0 milestone Nov 1, 2024
maumar added a commit that referenced this issue Nov 1, 2024
…llection/IList field

Problem was that when converting primitive collection to inline query root we were not matching the expression if it was constant wrapped in convert.
Fix is to remove convert for the purpose of pattern match for the transformation.

Fixes #35024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
3 participants