将C#表达式转换为现有的Queryable方法。

huangapple go评论80阅读模式
英文:

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>>,但是当我将Queryable方法传递给Expression.Call的参数时,它会抛出InvalidOperationException异常。

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&lt;TSource, IQueryable&lt;TResult&gt;&gt; sourceExpression;
Expression&lt;TSource, int, int, IQueryable&lt;TResult&gt;&gt; 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&lt;Func&lt;TSource, int, int, IQueryable&lt;TResult&gt;&gt;&gt;(
                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), &quot;skip&quot;);
var skipFunc = new Func&lt;IQueryable&lt;TResult&gt;, int, IQueryable&lt;TResult&gt;&gt;(Queryable.Skip).Method;
var takeParameter = Expression.Parameter(typeof(int), &quot;take&quot;);
var takeFunc = new Func&lt;IQueryable&lt;TResult&gt;, int, IQueryable&lt;TResult&gt;&gt;(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&lt;Func&lt;TSource, int, int, IQueryable&lt;TResult&gt;&gt;&gt;(
                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&lt;T&gt;int,因此您需要创建一个闭合方法:

var method = typeof(Queryable).GetMethod(nameof(Queryable.Skip), new Type[]
    {
        typeof(IQueryable&lt;&gt;).MakeGenericType(Type.MakeGenericMethodParameter(0)),
        typeof(int)
    })
    .MakeGenericMethod(typeof(TResult));

然后使用它来构建表达式。

另一种方法是让编译器自己解决所有问题,然后在需要时使用替换表达式树访问器,例如来自EF Core的访问器。大致如下(没有完整的重现,很难确定):

Expression&lt;Func&lt;IQueryable&lt;TResult&gt;, int, int, IQueryable&lt;TResult&gt;&gt;&gt; skipExpr = (source, skip, take) =&gt; 
    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&lt;T&gt; and int, so you need to create a closed method:

var method = typeof(Queryable).GetMethod(nameof(Queryable.Skip), new Type[]
    {
        typeof(IQueryable&lt;&gt;).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&lt;Func&lt;IQueryable&lt;TResult&gt;, int, int, IQueryable&lt;TResult&gt;&gt;&gt; skipExpr = (source, skip, take) =&gt; 
    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 &#39;skip&#39; parameter
var skipParameter = Expression.Parameter(typeof(int), &quot;skip&quot;);
var skipFunc = new Func&lt;IQueryable&lt;TResult&gt;, int, IQueryable&lt;TResult&gt;&gt;(Queryable.Skip).Method;

// Take() function from Queryable with &#39;take&#39; parameter
var takeParameter = Expression.Parameter(typeof(int), &quot;take&quot;);
var takeFunc = new Func&lt;IQueryable&lt;TResult&gt;, int, IQueryable&lt;TResult&gt;&gt;(Queryable.Take).Method;

// Create lambda expresion from source
var initialCall = Expression.Lambda&lt;Func&lt;TSource, IQueryable&lt;TResult&gt;&gt;&gt;(
                    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&lt;Func&lt;TSource, int, int, IQueryable&lt;TResult&gt;&gt;&gt;(
                    takeCall,
                    contextParameter,
                    skipParameter,
                    takeParameter);

That finally gives me the result I need.

huangapple
  • 本文由 发表于 2023年8月8日 22:48:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76860701.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定