我想以上面 Jon 的回答为起点分享我的实现。在这种情况下,我不是按来自表示层的字符串属性名称进行排序(因为这个问题的标题并不具体),而是构建一个实体框架数据层并希望允许其使用者指定顺序通过属性作为 lambda 表达式。 IE。我不想通过"sidx",而是希望能够使用p => p.sidx。我还希望能够通过属性传递无限数量的顺序,并能够指定升序或降序。
我的方法可以接受 Expression<Func<T, object>> 这样的 lambda 表达式。那让我按照我想要的方式来称呼它,但问题是,实体框架无法将表达式转换为 SQL,除非第二个泛型参数是强类型的。 OrderBy 扩展方法需要两个泛型参数:T - 属性所属的类型,以及 TKey - 属性返回的类型。所以第一步是修改 Jon 的示例,将给定的Expression<Func<T, object>> 转换为Expression<Func<T, Tkey>>(一旦我们在查询的上下文中工作,我们就可以确定TKey 的类型):
internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, Expression<Func<T, object>> sortExp)
{
//We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
//in order for Entity Framework to be able to translate it to SQL
MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExp.Body) as MemberExpression;
ParameterExpression sourceParam = sortExp.Parameters[0];
LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });
MethodInfo orderByMethod = OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });
//Call OrderBy or OrderByDescending on the source IQueryable<T>
return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
}
正如我所提到的,我希望通过键选择器接受无限数量的排序,并且还能够指定升序或降序方向,因此我为 Expression<Func<T, object>> 创建了一个包装类,我将其命名为 DynamicSortExpression:
public class DynamicSortExpression<T>
{
/// <summary>
/// Creates a new ascending DynamicSortExpression
/// </summary>
/// <param name="keySelector">A MemberExpression identifying the property to sort on</param>
public DynamicSortExpression(Expression<Func<T, object>> keySelector) : this(keySelector, false)
{
}
public DynamicSortExpression(Expression<Func<T, object>> keySelector, bool descending)
{
this.KeySelector = keySelector;
this.Desc = descending;
}
/// <summary>
/// Gets the expression that selects the property of T to sort on
/// </summary>
public Expression<Func<T, object>> KeySelector { get; }
/// <summary>
/// Gets sort expression is in ascending or descending order
/// </summary>
public bool Desc { get; }
}
然后我更新了扩展方法以接受这种类型,并为接收List<DynamicSortExpression<T>> 的OrderBy 创建了一个重载,并使用扩展方法将它们一一添加到查询中。这是最终结果:
public static class Extensions
{
private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods()
.Where(method => method.Name == "OrderBy")
.Where(method => method.GetParameters().Length == 2)
.Single();
private static readonly MethodInfo OrderByDescMethod = typeof(Queryable).GetMethods()
.Where(method => method.Name == "OrderByDescending")
.Where(method => method.GetParameters().Length == 2)
.Single();
private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods()
.Where(method => method.Name == "ThenBy")
.Where(method => method.GetParameters().Length == 2)
.Single();
private static readonly MethodInfo ThenByDescMethod = typeof(Queryable).GetMethods()
.Where(method => method.Name == "ThenByDescending")
.Where(method => method.GetParameters().Length == 2)
.Single();
internal static IQueryable<T> OrderBy<T>(this IQueryable<T> sourceQuery, List<DynamicSortExpression<T>> orderBy)
{
bool isFirst = true;
foreach (var sortExpression in orderBy)
{
if (isFirst)
{
sourceQuery = sourceQuery.OrderByDynamic(sortExpression);
isFirst = false;
}
else
sourceQuery = sourceQuery.ThenByDynamic(sortExpression);
}
return sourceQuery;
}
internal static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
{
//We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
//in order for Entity Framework to be able to translate it to SQL
MemberExpression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];
LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });
MethodInfo orderByMethod = sortExpression.Desc ?
OrderByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
OrderByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });
//Call OrderBy or OrderByDescending on the source IQueryable<T>
return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
}
internal static IQueryable<T> ThenByDynamic<T>(this IQueryable<T> source, DynamicSortExpression<T> sortExpression)
{
//We need to convert the key selector from Expression<Func<T, object>> to a strongly typed Expression<Func<T, TKey>>
//in order for Entity Framework to be able to translate it to SQL
Expression orderByMemExp = ExpressionHelpers.RemoveConvert(sortExpression.KeySelector.Body) as MemberExpression;
ParameterExpression sourceParam = sortExpression.KeySelector.Parameters[0];
LambdaExpression orderByLamda = Expression.Lambda(orderByMemExp, new[] { sourceParam });
MethodInfo orderByMethod = sortExpression.Desc ?
ThenByDescMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type }) :
ThenByMethod.MakeGenericMethod(new[] { typeof(T), orderByMemExp.Type });
//Call OrderBy or OrderByDescending on the source IQueryable<T>
return (IQueryable<T>)orderByMethod.Invoke(null, new object[] { source, orderByLamda });
}
}
现在我的数据层可以有一个像List<T> GetList(Expression<Func<T, bool>> where, params DynamicSortExpression<T>[] orderBy)这样的方法,可以像这样调用
new MyClass<Person>().GetList(p => p.FirstName == "Billy", //where clause
new DynamicSortExpression<Person>(p => p.FirstName),
new DynamicSortExpression<Person>(p => p.LastName, true));
在内部,MyClass<T>.GetList 构建调用我的 OrderBy 扩展方法的查询。比如:
DBContext.Set<T>().Where(whereParam).OrderBy(orderByParams);
RemoveConvert 方法是我从 EntityFramework 源代码中提取出来的,用于递归删除 MemberExpression 中的 Convert 调用:
internal static Expression RemoveConvert(Expression expression)
{
System.Diagnostics.Debug.Assert(expression != null);
while ((expression != null)
&& (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked))
{
expression = RemoveConvert(((UnaryExpression)expression).Operand);
}
return expression;
}
我希望这会有所帮助!谢谢乔恩!