我不明白接受的答案是如何被接受的答案,因为 OP 正在询问如何为多列排序场景创建表达式树。
有时您必须使用表达式树手动构建OrderBy语句,因为您不知道用户想要对哪些列进行排序。例如,当您有一个使用Datatables构建的网格并且某些列是可排序的时,用户可以 SHIFT 单击列标题以按多列排序:
屏幕截图显示用户希望按 Cassette(即 a string
)和 Slot Number(即 a double
)对网格进行排序。
OrderBy* / ThenBy*
构建用于排序的表达式树时,棘手的部分是第一次需要使用OrderBy*
,但第二次及以后需要切换到使用ThenBy*
。
我将演示如何使用扩展方法来做到这一点IQueryable
:
namespace DataTables.AspNet.Core
{
public interface ISort
{
int Order { get; }
SortDirection Direction { get; }
}
public enum SortDirection
{
Ascending = 0,
Descending = 1
}
}
namespace DL.SO.Framework.Mvc.DataTables.Extensions
{
public static class QueryableExtensions
{
public static IQueryable<TModel> OrderByColumns<TModel>(
this IQueryable<TModel> collection,
IDictionary<string, ISort> sortedColumns)
{
// Basically sortedColumns contains the columns user wants to sort by, and
// the sorting direction.
// For my screenshot, the sortedColumns looks like
// [
// { "cassette", { Order = 1, Direction = SortDirection.Ascending } },
// { "slotNumber", { Order = 2, Direction = SortDirection.Ascending } }
// ]
bool firstTime = true;
// The type that represents each row in the table
var itemType = typeof(TModel);
// Name the parameter passed into the lamda "x", of the type TModel
var parameter = Expression.Parameter(itemType, "x");
// Loop through the sorted columns to build the expression tree
foreach (var sortedColumn in sortedColumns.OrderBy(sc => sc.Value.Order))
{
// Get the property from the TModel, based on the key
var prop = Expression.Property(parameter, sortedColumn.Key);
// Build something like x => x.Cassette or x => x.SlotNumber
var exp = Expression.Lamda(prop, parameter);
// Based on the sorting direction, get the right method
string method = String.Empty;
if (firstTime)
{
method = sortedColumn.Value.Direction == SortDirection.Ascending
? "OrderBy"
: "OrderByDescending";
firstTime = false;
}
else
{
method = sortedColumn.Value.Direction == SortDirection.Ascending
? "ThenBy"
: "ThenByDescending";
}
// itemType is the type of the TModel
// exp.Body.Type is the type of the property. Again, for Cassette, it's
// a String. For SlotNumber, it's a Double.
Type[] types = new Type[] { itemType, exp.Body.Type };
// Build the call expression
// It will look something like:
// OrderBy*(x => x.Cassette) or Order*(x => x.SlotNumber)
// ThenBy*(x => x.Cassette) or ThenBy*(x => x.SlotNumber)
var mce = Expression.Call(typeof(Queryable), method, types,
collection.Expression, exp);
// Now you can run the expression against the collection
collection = collection.Provider.CreateQuery<TModel>(mce);
}
return collection;
}
}
}
注:OrderBy* 表示 OrderBy 或 OrderByDescending。ThenBy* 也是如此。