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 );
关键说明
- 参数一致性:Lambda的参数列表包含原表达式的
TSource参数,以及新增的skip、take参数,确保表达式内部使用的参数与Lambda声明的参数完全对应。 - 静态方法调用逻辑:调用
Queryable.Skip/Take时,严格遵循方法定义:第一个参数是IQueryable<TResult>类型的原表达式结果,第二个是int类型的分页参数,类型参数指定TResult。 - 避免Invoke:直接基于
sourceExpression.Body构建链式调用,无需通过Expression.Invoke执行原表达式,生成的表达式可被ORM正常解析。
内容的提问来源于stack exchange,提问作者Vlad Nan
相关产品推荐
相关产品推荐

