[C#]使用IQueryable動態排序ThenBy
該情境是前端有資料需要多重排序,每個欄位(key)可以正序或倒序(order),且要依照順序(sortNo),所以前端只要把排序條件給後端,而後端會把資料篩選排序後,回傳給前端。
由於效能因素,物件必須於過程中保持 IQueryable,於最後輸出時再轉為 IEnumerable,所以這邊有個擴充可以參考引用。
●排序條件的範例Json:
{
"orderList": [
{
"key": "Id".
"order": 1,
"sortNo": 1
},
{
"key": "Age".
"order": -1,
"sortNo": 2
}
]
}
一、手動新增IQueryable擴充:
public static class IOrderedQueryableExtension
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
string[] props = property.Split('.');
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (string prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
PropertyInfo pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IOrderedQueryable<T>)result;
}
}
二、使用IQueryable擴充:
1. 先定義排序的Class
public class DynamicOrder
{
public string Key { get; set; } // 排序Key (對應到Model的Property)
public int Order { get; set; } // 排序種類 (正序: 1,倒序: -1)
public int SortNo { get; set; } // 排序順序 (該Key要第幾個順位排序)
}
public class InputModel
{
// TODO: 定義Model的屬性
// Ex: public int Id { get; set; }
}
2. 實作排序方法
private IQueryable<InputModel>ExcuteDynamicOrder(IQueryable<InputModel> inputData, List<DynamicOrder> orderList)
{
// 需在 Expression 中加入 OrderBy,後續才可以使用 ThenBy
var orderData = inputData.OrderBy(p => 0);
var type = typeof(InputModel);
var orders = new List<DynamicOrder>();
// 若傳入的 排序key 不在 InputModel 的 Property 中,這邊會過濾掉
foreach (var item in orderList)
{
foreach (var prop in type.GetProperties())
{
if (prop.Name.ToUpper() == item.Key?.ToUpper())
{
orders.Add(new DynamicOrder()
{
Key = prop.Name,
Order = item.Order,
SortNo = item.SortNo,
});
}
}
}
// 若過濾後的排序條件資料數量大於0,就進行排序
if (orders.Count > 0)
{
orders.OrderBy(p => p.SortNo).ToList().ForEach(item =>
{
if (item.Order == 1) // 正序
{
orderData = orderData.ThenBy(item.Key);
}
else if (item.Order == -1) // 倒序
{
orderData = orderData.ThenByDescending(item.Key);
}
});
}
inputData = orderData;
return inputData;
}
3. 調用排序方法 ( 第一個參數是IQueryable物件,第二個參數是排序條件 )
// 調用排序方法
var result = ExcuteDynamicOrder(iQueryableData, orderConditionList);
參考資料:
1. system.reflection - Entity framework ordering using reflection - Stack Overflow
留言
張貼留言