0

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()?

4
  • Expression<TSource, int, int, IQueryable<TResult>> resultExpression = (source, skip, take) => source.Skip(skip).Take(take); maybe? Commented Aug 8, 2023 at 14:57
  • yes, something like this, but source should come from sourceExpression which has return type IQueryable<TResult> and I need to pass it somehow Commented Aug 8, 2023 at 15:17
  • So put it in a function Expression<TSource, int, int, IQueryable<TResult>> SkipTakeExpr(this Expression<TSource, IQueryable<TResult>> sourceExpression) { return (source, skip, take) => source.Skip(skip).Take(take); } Commented Aug 8, 2023 at 15:28
  • "sourceExpression is not used and can be removed"... Commented Aug 8, 2023 at 16:10

2 Answers 2

1

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);
Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.