ExpressionTree ExpressionVisitor来更改/替换查询OrderBy字段

时间:2019-10-07 11:48:08

标签: c# .net linq expression-trees

在转换为sql之前,我需要能够更改表达式树上的orderby字段。 例如,如果查询包含一个orderby“ className.PropertyA”,则需要将其修改为orderby“ className.PropertyB” 到目前为止,我的想法是编写一个表达式访问器以更改树上的orderby方法节点。我的代码看起来像这样

public class ClassName
{
    public string PropertyA { get; set; }

    public string PropertyB { get; set; }

    public string PropertyC { get; set; }

    public string PropertyD { get; set; }
}

public class ChangeOrderByVisitor : ExpressionVisitor
{

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.DeclaringType == typeof(Queryable) &&
            (node.Method.Name == "OrderBy" || node.Method.Name == "OrderByDescending"))
        {
            //Only if ordering by className.PropertyA
                    //Somehow change the order by arguments.operands.body from className.PropertyA to className.PropertyB
        }

        return base.VisitMethodCall(node);
    }

}

在某些时候,该表达式树将转换为sql,并且在对数据库执行时应按className.PropertyB进行排序。

谢谢。

2 个答案:

答案 0 :(得分:0)

将此作为一个不错的编程练习:-)

这是一个处理反射和表达式的类:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class MyExtensions
{
    /// <summary>
    /// Helper method to reflect the return type.
    /// </summary>
    public static Expression<Func<T1, TResult>> FuncX<T1, TResult>(Expression<Func<T1, TResult>> lambda)
        => lambda;

    /// <summary>
    /// Helper method to get a <see cref="MemberInfo"/>.
    /// </summary>
    public static MemberInfo GetMember<TSource, TResult>(this Expression<Func<TSource, TResult>> lambda)
        => (lambda?.Body as MemberExpression)?.Member ?? throw new ArgumentException($"Not a {nameof(MemberExpression)}.");

    /// <summary>
    /// Helper method to get a <see cref="MethodInfo"/>.
    /// </summary>
    public static MethodInfo GetMethod<TSource, TResult>(this Expression<Func<TSource, TResult>> lambda)
        => (lambda?.Body as MethodCallExpression)?.Method ?? throw new ArgumentException($"Not a {nameof(MethodCallExpression)}.");

    /// <summary>
    /// <see cref="Queryable.OrderBy{TSource,TKey}(System.Linq.IQueryable{TSource},System.Linq.Expressions.Expression{System.Func{TSource,TKey}})"/>
    /// </summary>
    private static readonly MethodInfo _miOrderBy = GetMethod((IQueryable<int> q) => q.OrderBy(x => x)).GetGenericMethodDefinition();

    /// <summary>
    /// Replace occurrencies of OrderBy(<paramref name="origKeySelector"/>) with OrderBy(<paramref name="newKeySelector"/>).
    /// </summary>
    /// <exception cref="ArgumentException"><paramref name="origKeySelector"/>'s body is not a <see cref="MemberExpression"/>.</exception>
    public static IQueryable<TQueryable> ChangeOrder<TQueryable, TOrdered, TOrigOrder, TNewOrder>(this IQueryable<TQueryable> queryable, Expression<Func<TOrdered, TOrigOrder>> origKeySelector, Expression<Func<TOrdered, TNewOrder>> newKeySelector)
    {
        var changed = new ChangeOrderVisitor<TOrdered, TOrigOrder, TNewOrder>(origKeySelector, newKeySelector).Visit(queryable.Expression);
        return queryable.Provider.CreateQuery<TQueryable>(changed);
    }

    private sealed class ChangeOrderVisitor<TOrdered, TOrigOrder, TNewOrder> : ExpressionVisitor
    {
        private static readonly MethodInfo _miOrigOrderBy = _miOrderBy.MakeGenericMethod(typeof(TOrdered), typeof(TOrigOrder));
        private static readonly MethodInfo _miNewOrderBy = _miOrderBy.MakeGenericMethod(typeof(TOrdered), typeof(TNewOrder));

        private readonly MemberInfo _origMember;
        private readonly Expression<Func<TOrdered, TNewOrder>> _newKeySelector;

        public ChangeOrderVisitor(Expression<Func<TOrdered, TOrigOrder>> origKeySelector, Expression<Func<TOrdered, TNewOrder>> newKeySelector)
        {
            _origMember = origKeySelector.GetMember();
            _newKeySelector = newKeySelector;
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method == _miOrigOrderBy)
            {
                if (node.Arguments[1] is UnaryExpression u &&
                    u.Operand is LambdaExpression lambda &&
                    lambda.Body is MemberExpression mx &&
                    mx.Member == _origMember)
                    return Expression.Call(_miNewOrderBy, Visit(node.Arguments[0]), _newKeySelector);
            }
            return base.VisitMethodCall(node);
        }
    }
}

这是测试代码:

var origOrder = MyExtensions.FuncX((Person p) => p.FirstName);
var qOrig = new[]
{
    new Person{ FirstName = "Elon", LastName = "Musk", ShoeSize = 44 },
    new Person{ FirstName = "Jeff", LastName = "Who?", ShoeSize = 40 }
}
    .AsQueryable()
    .OrderBy(origOrder)
    .Select(p => p.LastName);
var qChanged = qOrig.ChangeOrder(origOrder, x => x.ShoeSize); // <string, Person, string, int>
var result = qChanged.ToList(); // "Who?", "Musk"

答案 1 :(得分:0)

尽管您可以在运行时通过反射修改表达式,但为简单起见,我建议您采用其他方法。

我建议您将按参数存储的顺序存储在表达式外部,直到确定要编译表达式为止。

public class OrderByArgumentProvider<TSource> {
    private Expression<Func<TSource, object>> _keySelector;

    public void Update(Expression<Func<TSource, object>> newKey) {
        _keySelector = newKey;
    }

    public Expression<Func<TSource, object>> GetKeySelector() {
        return _keySelector;
    }
}

然后您可以像这样使用它:

var orderByArgumentProvider = new OrderByArgumentProvider<Model>();

orderByArgumentProvider.Update(x => x.Id);

_context.Table.OrderBy(orderByArgumentProvider.GetKeySelector());