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()));
How to convert
Func<T, bool>
toExpression<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 queryToQueryString()
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:
Introduce GenerateFilterExpression
method which will actually build translatable Expression<Func<T, bool>>
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);
}
Use PredicateBuilder
from LINQKit (possibly in addition to point 2)
See also: