You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中移除表达式树Invoke调用,拼接Skip/Take到Queryable表达式

如何在C# Expression中组合Queryable.Skip()和Queryable.Take()到现有表达式

需求场景

现有表达式 Expression<TSource, IQueryable<TResult>> sourceExpression,需要将Queryable.Skip()和Queryable.Take()方法追加到该表达式后,生成新的表达式 Expression<TSource, int, int, IQueryable<TResult>> resultExpression。

之前的尝试及问题

尝试1:直接用Expression.Call构建

代码示例:

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

问题原因:创建的两个int类型参数未加入最终Lambda的参数集合,导致表达式内部使用的参数与Lambda声明的参数不匹配;同时调用Queryable静态方法时的参数绑定逻辑存在错误。

尝试2:通过指定Func方法构建

代码示例:

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);

问题:生成的表达式包含Invoke()调用,后续无法被ORM(如EF Core)转换为可执行的SQL,因为Invoke不属于可解析的表达式节点。

正确实现方案

要避免Invoke并解决参数不匹配问题,需直接复用原表达式的Body,同时将新增的分页参数纳入Lambda的参数列表,正确调用Queryable静态方法:

// 定义分页参数
var skipParam = Expression.Parameter(typeof(int), "skip");
var takeParam = Expression.Parameter(typeof(int), "take");

// 构建Skip调用:基于原表达式的结果,传入skip参数
var skipCall = Expression.Call(
    typeof(Queryable),
    nameof(Queryable.Skip),
    new[] { typeof(TResult) },
    sourceExpression.Body,
    skipParam
);

// 构建Take调用:基于Skip的结果,传入take参数
var takeCall = Expression.Call(
    typeof(Queryable),
    nameof(Queryable.Take),
    new[] { typeof(TResult) },
    skipCall,
    takeParam
);

// 构建最终Lambda:参数包含原TSource参数+新增的两个分页参数
resultExpression = Expression.Lambda<Func<TSource, int, int, IQueryable<TResult>>>(
    takeCall,
    sourceExpression.Parameters[0],
    skipParam,
    takeParam
);

关键说明

  1. 参数一致性:Lambda的参数列表包含原表达式的TSource参数,以及新增的skip、take参数,确保表达式内部使用的参数与Lambda声明的参数完全对应。
  2. 静态方法调用逻辑:调用Queryable.Skip/Take时,严格遵循方法定义:第一个参数是IQueryable<TResult>类型的原表达式结果,第二个是int类型的分页参数,类型参数指定TResult。
  3. 避免Invoke:直接基于sourceExpression.Body构建链式调用,无需通过Expression.Invoke执行原表达式,生成的表达式可被ORM正常解析。

内容的提问来源于stack exchange,提问作者Vlad Nan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:14:53