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

问题描述 投票:0回答:1

我必须能够在表达式树上更改orderby字段,然后将其转换为sql。例如,如果查询包含一个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排序。

谢谢。

c# .net linq expression-trees
1个回答
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"
© www.soinside.com 2019 - 2024. All rights reserved.