在转换为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进行排序。
谢谢。
答案 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());