英文:
Convert Expressions C# with existing Queryable methods
问题
我需要将方法附加到现有的表达式中,并将它们组合成一个新的结果表达式。
Expression<TSource, IQueryable<TResult>> sourceExpression;
Expression<TSource, int, int, IQueryable<TResult>> resultExpression;
我需要将Queryable.Skip()和Queryable.Take()方法附加到sourceExpression,并将它们一起转换为resultExpression。如何使用C#的Expression方法来实现呢?
我尝试使用Expression.Call和Expression.Lambda<Func<TSource, int, int, IQueryable
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
);
类型为'System.Linq.Queryable'的对象上没有泛型方法'Take'与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。
...
所以我可以使用Expression.Call和所需的Queryable函数构建最终的resultExpression。
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()方法,它无法进一步翻译。(source, skip, take)=> Invoke(source => // sourceExpression...), source).Skip(skip).Take(take)
如何去掉包装的Invoke()呢?
英文:
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<TResult>>> 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()?
答案1
得分: 1
Skip
是一个通用方法,至少接受两个参数,即传入的IQueryable<T>
和int
,因此您需要创建一个闭合方法:
var method = typeof(Queryable).GetMethod(nameof(Queryable.Skip), new Type[]
{
typeof(IQueryable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
typeof(int)
})
.MakeGenericMethod(typeof(TResult));
然后使用它来构建表达式。
另一种方法是让编译器自己解决所有问题,然后在需要时使用替换表达式树访问器,例如来自EF Core的访问器。大致如下(没有完整的重现,很难确定):
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);
英文:
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);
答案2
得分: 0
所以事实证明,我需要使用Body作为参数从原始表达式构建lambda表达式
// 使用'skip'参数从Queryable中获取Skip()函数
var skipParameter = Expression.Parameter(typeof(int), "skip");
var skipFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Skip).Method;
// 使用'take'参数从Queryable中获取Take()函数
var takeParameter = Expression.Parameter(typeof(int), "take");
var takeFunc = new Func<IQueryable<TResult>, int, IQueryable<TResult>>(Queryable.Take).Method;
// 从源表达式创建lambda表达式
var initialCall = Expression.Lambda<Func<TSource, IQueryable<TResult>>>(
sourceExpression.Body,
sourceExpression.Parameters[0]);
// 添加Skip函数
var skipCall = Expression.Call(
skipFunc,
initialCall.Body,
skipParameter);
// 添加Take函数
var takeCall = Expression.Call(
takeFunc,
skipCall,
takeParameter);
// 将调用包装在最终表达式中
resultExpression = Expression.Lambda<Func<TSource, int, int, IQueryable<TResult>>>(
takeCall,
contextParameter,
skipParameter,
takeParameter);
这最终给我提供了我所需要的结果。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论