[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

留言

這個網誌中的熱門文章

[Note] 公司常見的書信結尾

[C#] 無法載入檔案或組件 或其相依性的其中之一。 找到的組件資訊清單定義與組件參考不符。 (發生例外狀況於 HRESULT: 0x80131040)

[IIS] 拒絕存取路徑 & 開啟資料夾權限