Search code examples
c#lambdaentity-framework-coreexpressionexpression-trees

How to convert Func<T, bool> to Expression<Func<T, bool>>


I use this code but it gets an error that:

Error: System.ArgumentException: Expression of type '...GreenPaperItem' cannot be used for parameter of type 'System.Runtime.CompilerServices.Closure' of method 'Boolean

public static Expression<Func<T, bool>> ToExpression<T>(Func<T, bool> p)
{
     ParameterExpression p0 = Expression.Parameter(typeof(T));
     ParameterExpression p1 = Expression.Parameter(typeof(bool));
     return Expression.Lambda<Func<T, bool>>(Expression.Call(p.Method, p0, p1),
           new ParameterExpression[] { p0, p1 });
}

the expression is intended to be used in a linq to entities IQueriyable query:

 query = query.Where(ToExpression<GreenPaperItem>(filter.GenerateFilterFunction()));

Solution

  • How to convert Func<T, bool> to Expression<Func<T, bool>>

    In short - you don't. The vice versa conversion is "easy" via provided by the framework Expression<TDelegate>.Compile method. But other way around will require performing something like decompilation from IL/machine code (I think I saw some attempts but not sure where and when =) ).

    I used AsQueryable() and it works, but for checking that it is not enumerable at the end, I get the error "The given 'IQueryable' does not support generation of query strings." when I want to convert the query ToQueryString()

    So basically it does not work (at least in the way you actually want/need too).

    It seems that you have some misunderstanding how IQueryable, expression trees and query providers (especially EF Core one) work. In short (with some simplifications) usually query provider will analyze the passed expression tree to perform some actions. In EF Core case it will translate the calls into actual SQL query and it can't translate an arbitrary method call into SQL, which can't be fixed by "converting" Func<...> returned by filter.GenerateFilterFunction() to Expression<Func<...>>.

    Your not working attempt:

    public static Expression<Func<T, bool>> ToExpression<T>(Func<T, bool> p)
    {
         ParameterExpression p0 = Expression.Parameter(typeof(T));
         ParameterExpression p1 = Expression.Parameter(typeof(bool));
         return Expression.Lambda<Func<T, bool>>(Expression.Call(p.Method, p0, p1),
               new ParameterExpression[] { p0, p1 });
    }
    

    is not that much different from:

    query.Where(gpi => filter.GenerateFilterFunction()(gpi)) // allow the compiler to do the magic for you
    

    Which will have the same issue - GenerateFilterFunction returns the function query provider (EF Core) knows nothing about and can't translate it to SQL (for obvious reasons).

    And your attempt from the comments:

    query = query
       .Where(filter.GenerateFilterFunction())
       .AsQueryable();
    

    Will result in that everything since .Where(filter.GenerateFilterFunction()) will be executed on the client side fetching everything into memory without filtering on the database side (Enumerable.Where(Func<>) will be used instead of Queryable.Where(Expression<Func<>>), basically the same effect as explicit AsEnumerable() call before Where). The following AsQueryable does not have any significant effect since it will be queryable over in-memory objects. This is also proved by the observed behavior of EntityFrameworkQueryableExtensions.ToQueryString(IQueryable).

    You have several options:

    1. Introduce GenerateFilterExpression method which will actually build translatable Expression<Func<T, bool>>

    2. Quite usual approach is to just leverage ability to "dynamically" compose Where's (when you need AND combination logic):

      if(filter.IsProp1Provided)
      {
          query = query.Where(e => e.Col1 == filter.Prop1);
      }
      
      if(filter.IsAnotherPropProvided)
      {
          query = query.Where(e => e.AnotherCol == filter.AnotherProp);
      }
      
    3. Use PredicateBuilder from LINQKit (possibly in addition to point 2)

    See also: