Reputation: 71
I need to append methods to an existion experssion and combine them into a new resultExpression.
Expression<TSource, IQueryable<TResult>> sourceExpression;
Expression<TSource, int, int, IQueryable<TResult>> resultExpression;
I need to append Queryable.Skip() and Queryable.Take() methods to sourceExpression and convert altogether to a resultExpression. How can I do it using c# Expression methods?
I tried to use Expression.Lambda<Func<TSource, int, int, IQueryable>> with Expression.Call, but it throws InvalidOperationException when I pass Queryable methods to Expression.Call parameters
var skipCall = Expression.Call(
typeof(Queryable),
nameof(Queryable.Skip),
new[] {typeof(TResult)},
sourceExpression.Body,
Expression.Parameter(typeof(int))
);
var takeCall = Expression.Call(
typeof(Queryable),
nameof(Queryable.Take),
new[] {typeof(TResult)},
skipCall,
Expression.Parameter(typeof(int))
);
var resultExpression = Expression.Lambda<Func<TSource, int, int, IQueryable<TResult>>>(
takeCall, sourceExpression.Parameters
);
No generic method 'Take' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic
...
So I'm able to build final resultExpression using Expression.Call with Func needed from Queryable
var skipParameter = Expression.Parameter(typeof(int), "skip");
var skipFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Skip).Method;
var takeParameter = Expression.Parameter(typeof(int), "take");
var takeFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Take).Method;
var initialCall = Expression.Invoke(sourceExpression,sourceExpression.Parameters[0]);
var skipCall = Expression.Call(
skipFunc,
initialCall,
skipParameter);
var takeCall = Expression.Call(
takeFunc,
skipCall,
takeParameter);
resultExpression = Expression.Lambda<Func<TSource, int, int, IQueryable<TResult>>>(
takeCall,
sourceExpression.Parameters[0],
skipParameter,
takeParameter);
however I get Invoke() method in resulted expression string which can't be translated further. (source, skip, take) => Invoke(source => // sourceExpression...), source).Skip(skip).Take(take) How can I get rid of wrapping Invoke()?
Upvotes: 0
Views: 101
Reputation: 71
So it turns out that I need to build lambda from original expression with Body as a parameter
// Skip() function from Queryable with 'skip' parameter
var skipParameter = Expression.Parameter(typeof(int), "skip");
var skipFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Skip).Method;
// Take() function from Queryable with 'take' parameter
var takeParameter = Expression.Parameter(typeof(int), "take");
var takeFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Take).Method;
// Create lambda expresion from source
var initialCall = Expression.Lambda<Func<TSource, IQueryable<TResult>>>(
sourceExpression.Body,
sourceExpression.Parameter[0]);
// Append Skip function
var skipCall = Expression.Call(
skipFunc,
initialCall.Body,
skipParameter);
// Append Take function
var takeCall = Expression.Call(
takeFunc,
skipCall,
takeParameter);
// Wrap calls in the final expression
resultExpression = Expression.Lambda<Func<TSource, int, int, IQueryable<TResult>>>(
takeCall,
contextParameter,
skipParameter,
takeParameter);
That finally gives me the result I need.
Upvotes: 0
Reputation: 143328
Skip
is generic and accepts at least 2 parameters, incoming IQueryable<T>
and int
, so you need to create a closed method:
var method = typeof(Queryable).GetMethod(nameof(Queryable.Skip), new Type[]
{
typeof(IQueryable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
typeof(int)
})
.MakeGenericMethod(typeof(TResult));
And then use it to build expressions.
Another approach would be to let compiler to figure everything out and then using replacing expression tree visitor if needed, for example one from EF Core. Something along these lines (it is hard to tell without full repro):
Expression<Func<IQueryable<TResult>, int, int, IQueryable<TResult>>> skipExpr = (source, skip, take) =>
source.Skip(skip).Take(take);
var result = new ReplacingExpressionVisitor(skipExpr.Parameters, sourceExpression.Parameters)
.Visit(skipExpr.Body);
Upvotes: 1