.net-coreentity-framework-corenpgsql

EF Core 6: .Any() in query filter produce "could not be translated. Either rewrite the query in a form that can be translated"


I have Product entity and write a method in a repository class (using npgsql). But I get error :

could not be translated. 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. System.InvalidOperationException: The LINQ expression 's => s.ProductId == EntityShaperExpression:

public class Product:IEntity
{
  public string Id { get; set; }
  public string SerialNo { get; set; }
  public string Imei { get; set; }
  public string Name { get; set; }
}   

public class ProductInput
{
  public string ProductId { get; set; }
  public string SerialNo { get; set; }
  public string Imei { get; set; }
}

public async Task<List<Product>> GetProducts(List<ProductInput> input) //EfCoreProductRepository.cs
{
      var result = (from product in (await GetDbContextAsync()).Products
                    where input.Any(s => s.ProductId == product.Id && s.SerialNo == product.SerialNo && s.Imei == product.Imei)
                    select product).ToList();
    return result;
}

How should I edit my query to avoid getting this error?


Solution

  • It looks like you're trying to retreieve DB rows that match your input rows by multiple criteria. There are multiple ways to do this, such as:

    In this author's opinion, Option 2 (Dynamically-built Linq Query) is the "best" overall here, so do it like so:

    First, get PredicateBuilder - it's a tiiiiiny dependency that makes it significantly easier to build-up Or predicates with Linq. It's literally a class you copy-and-paste from Albahari's website.

    Second, use it like so:

    public async Task<List<Product>> GetProductsAsync( List<ProductInput> input, CancellationToken cancellationToken )
    {
        DbSet<Product> dbSet = await this.GetDbSetAsync();
    
        var predicate = PredicateBuilder.False<Product>();
        foreach( ProductInput pi in input )
        {
            predicate = predicate.Or( p => (
                p.ProductId == pi.Id
                &&
                p.SerialNo == pi.SerialNo
                &&
                p.Imei == pi.Imei
            ) );
        }
    
        IQueryable<Product> query = dbSet.Where( predicate );
    
        List<Product> results = await query.ToListAsync(cancellationToken);
        return results;
    }
    

    PredicateBuilder

    For posterity, I've reproduced class PredicateBuilder here:

    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Collections.Generic;
     
    public static class PredicateBuilder
    {
      public static Expression<Func<T,Boolean>> True<T> ()  => ( f => true );
      public static Expression<Func<T,Boolean>> False<T> () => ( f => false );
     
      public static Expression<Func<T,Boolean>> Or<T>(this Expression<Func<T,Boolean>> expr1, Expression<Func<T,Boolean>> expr2)
      {
        var invokedExpr = Expression.Invoke(
          expr2,
          expr1.Parameters.Cast<Expression>()
        );
    
        return Expression.Lambda<Func<T,Boolean>>(
          Expression.OrElse(expr1.Body, invokedExpr),
          expr1.Parameters
        );
      }
     
      public static Expression<Func<T,Boolean>> And<T>(this Expression<Func<T,Boolean>> expr1, Expression<Func<T,Boolean>> expr2)
      {
        var invokedExpr = Expression.Invoke(
          expr2,
          expr1.Parameters.Cast<Expression>()
        );
        
        return Expression.Lambda<Func<T,Boolean>>(
          Expression.AndAlso(expr1.Body, invokedExpr),
          expr1.Parameters
        );
      }
    }