在提供的 "表达式 "上添加 "方法调用表达式"。[英] Adding `MethodCallExpression` on top of provided `Expression`

本文是小编为大家收集整理的关于在提供的 "表达式 "上添加 "方法调用表达式"。的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我有源自 Linq 的表达式树,例如leCollection.Where(...).OrderBy(...).Skip(n).Take(m).表达式如下:

Take(Skip(OrderBy(Where(...), ...), n), m) // you got the idea

现在,这是我有 Take 和 Skip 的理想状态,但这不是规则.如果需要,我想以编程方式添加 Take/Skip.

我想出了如何更改 Take/Skip 参数的方法,如果我检测到它不存在,我什至可以在 Take 下添加 Skip,但我正在努力弄清楚如何在表达式顶部添加 Take - 我不知道如何识别我实际上正在访问顶部表达式.我编写的方法在树中的每个方法调用上都会执行,因此在对表达式进行任何操作之前,我必须检查方法名称.

以下是我用于更改 Take/Skip 并在 Take 下添加 Skip 的方法.这些工作,我现在也有兴趣将 Take 放在树的顶部,如果它还没有出现的话.谁能指导我到任何智慧的地方,在那里我可以学到更多?

public class LeVisitor<TEntity> : ExpressionVisitor
    where TEntity : class
{
    private readonly int? _take;
    private readonly int? _skip;
    private readonly MethodInfo _queryableSkip;

    public LeVisitor(int? take, int? skip)
    {
        // ...
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        return base.VisitMethodCall(AlterTake(AlterSkip(node)));
    }

    private MethodCallExpression AlterTake(MethodCallExpression node)
    {
        if (!_take.HasValue || !node.Method.Name.Equals("Take", StringComparison.Ordinal))
        {
            return node;
        }

        Expression innerCall = node.Arguments[0];
        if (_skip != null)
        {
            var innerMethod = innerCall as MethodCallExpression;
            if (innerMethod != null && !innerMethod.Method.Name.Equals("Skip", StringComparison.Ordinal))
            {
                ConstantExpression skipConstant = Expression.Constant(_skip, typeof(int));
                innerCall = Expression.Call(_queryableSkip, new[] { innerCall, skipConstant });
            }
        }

        return node.Update(
            node.Object,
            new[]
            {
                innerCall,
                Expression.Constant(_take, typeof(int))
            });
    }

    private MethodCallExpression AlterSkip(MethodCallExpression node)
    {
        if (!_skip.HasValue || !node.Method.Name.Equals("Skip", StringComparison.Ordinal))
        {
            return node;
        }

        return node.Update(
            node.Object,
            new[]
            {
                node.Arguments[0],
                Expression.Constant(_skip, typeof(int))
            });
    }
}

推荐答案

您可以覆盖 Visit 方法并使用标志变量来检查这是否是第一次调用它.
下一个代码将检查一个顶级方法,如果它不是一个 Take 添加对 Queryable.Take

的调用
public class AddTakeVisitor : ExpressionVisitor
{
    private readonly int takeAmount;
    private bool firstEntry = true;

    public AddTakeVisitor(int takeAmount)
    {
        this.takeAmount = takeAmount;
    }

    public override Expression Visit(Expression node)
    {
        if (!firstEntry)
            return base.Visit(node);

        firstEntry = false;
        var methodCallExpression = node as MethodCallExpression;
        if (methodCallExpression == null)
            return base.Visit(node);

        if (methodCallExpression.Method.Name == "Take")
            return base.Visit(node);

        var elementType = node.Type.GetGenericArguments();
        var methodInfo = typeof(Queryable)
            .GetMethod("Take", BindingFlags.Public | BindingFlags.Static)
            .MakeGenericMethod(elementType.First());
        return Expression.Call(methodInfo, node, Expression.Constant(takeAmount));
    }
}

我已经用这段代码测试过了:

var exp = (new[] {1, 2, 3}).AsQueryable().Skip(1);
var visitor = new AddTakeVisitor(1);
var modified = visitor.Visit(exp.Expression);

modified.DebugView 看起来像这样:

.Call System.Linq.Queryable.Take(
    .Call System.Linq.Queryable.Skip(
        .Constant<System.Linq.EnumerableQuery`1[System.Int32]>(System.Int32[]),
        1),
    1)

本文地址:https://www.itbaoku.cn/post/1556949.html

问题描述

I'm having expression tree originating in Linq, e.g. leCollection.Where(...).OrderBy(...).Skip(n).Take(m). Expression looks like:

Take(Skip(OrderBy(Where(...), ...), n), m) // you got the idea

Now, this is my ideal state that I have Take and Skip there, but it is not the rule. I would like to add Take/Skip programmatically if needed.

I came up with way how to change Take/Skip argument, and I'm even able to add Skip under Take if I detect it's not present, but I'm struggling to figure out how to add Take at the top of expression - I don't know how to recognize I'm actually visiting top expression. Methods I wrote are executed on every method call in tree, so I had to check method name before I do anything with expression.

Here are methods I'm using for altering Take/Skip and adding Skip under Take. Those work, I'm now also interested in placing Take on top of tree if it's not yet present. Could anyone direct me to any place of wisdom, where I can learn more?

public class LeVisitor<TEntity> : ExpressionVisitor
    where TEntity : class
{
    private readonly int? _take;
    private readonly int? _skip;
    private readonly MethodInfo _queryableSkip;

    public LeVisitor(int? take, int? skip)
    {
        // ...
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        return base.VisitMethodCall(AlterTake(AlterSkip(node)));
    }

    private MethodCallExpression AlterTake(MethodCallExpression node)
    {
        if (!_take.HasValue || !node.Method.Name.Equals("Take", StringComparison.Ordinal))
        {
            return node;
        }

        Expression innerCall = node.Arguments[0];
        if (_skip != null)
        {
            var innerMethod = innerCall as MethodCallExpression;
            if (innerMethod != null && !innerMethod.Method.Name.Equals("Skip", StringComparison.Ordinal))
            {
                ConstantExpression skipConstant = Expression.Constant(_skip, typeof(int));
                innerCall = Expression.Call(_queryableSkip, new[] { innerCall, skipConstant });
            }
        }

        return node.Update(
            node.Object,
            new[]
            {
                innerCall,
                Expression.Constant(_take, typeof(int))
            });
    }

    private MethodCallExpression AlterSkip(MethodCallExpression node)
    {
        if (!_skip.HasValue || !node.Method.Name.Equals("Skip", StringComparison.Ordinal))
        {
            return node;
        }

        return node.Update(
            node.Object,
            new[]
            {
                node.Arguments[0],
                Expression.Constant(_skip, typeof(int))
            });
    }
}

推荐答案

You can override Visit method and use flag variable to check if this is a very first call to it.
Next code will check a top method and if it's not a Take add call to Queryable.Take

public class AddTakeVisitor : ExpressionVisitor
{
    private readonly int takeAmount;
    private bool firstEntry = true;

    public AddTakeVisitor(int takeAmount)
    {
        this.takeAmount = takeAmount;
    }

    public override Expression Visit(Expression node)
    {
        if (!firstEntry)
            return base.Visit(node);

        firstEntry = false;
        var methodCallExpression = node as MethodCallExpression;
        if (methodCallExpression == null)
            return base.Visit(node);

        if (methodCallExpression.Method.Name == "Take")
            return base.Visit(node);

        var elementType = node.Type.GetGenericArguments();
        var methodInfo = typeof(Queryable)
            .GetMethod("Take", BindingFlags.Public | BindingFlags.Static)
            .MakeGenericMethod(elementType.First());
        return Expression.Call(methodInfo, node, Expression.Constant(takeAmount));
    }
}

I've tested it with this code:

var exp = (new[] {1, 2, 3}).AsQueryable().Skip(1);
var visitor = new AddTakeVisitor(1);
var modified = visitor.Visit(exp.Expression);

modified.DebugView looks like this:

.Call System.Linq.Queryable.Take(
    .Call System.Linq.Queryable.Skip(
        .Constant<System.Linq.EnumerableQuery`1[System.Int32]>(System.Int32[]),
        1),
    1)