首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >更改/替换查询OrderBy字段的ExpressionTree ExpressionVisitor

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

Stack Overflow用户
提问于 2019-10-07 19:48:09
回答 2查看 443关注 0票数 0

我需要能够在表达式树上更改orderby字段,然后才能将其转换为sql。例如,如果查询包含orderby "className.PropertyA",我需要将其修改为orderby "className.PropertyB“。到目前为止,我的想法是编写一个表达式访问器来更改树上的orderby方法节点。我的代码如下所示

代码语言:javascript
复制
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排序。

谢谢。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-10-07 23:22:23

认为这是一个很好的编程练习:-)

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

代码语言:javascript
复制
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);
        }
    }
}

下面是测试代码:

代码语言:javascript
复制
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"
票数 0
EN

Stack Overflow用户

发布于 2019-10-07 23:42:35

虽然您可以在运行时通过反射修改表达式,但为了简单起见,我建议您采用不同的方法。

我建议您将order by参数存储在表达式之外,直到您确定要编译表达式为止。

代码语言:javascript
复制
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;
    }
}

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

代码语言:javascript
复制
var orderByArgumentProvider = new OrderByArgumentProvider<Model>();

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

_context.Table.OrderBy(orderByArgumentProvider.GetKeySelector());
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58268966

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档