在處理 EasyUI DataGrid 的伺服器端分頁與排序時,傳統寫法需要針對每個欄位撰寫 switch-case 判斷,不僅程式碼冗長難維護,還容易出現拼字錯誤(如將 sort 寫成 sord 導致排序失效)。本篇介紹如何運用 Expression Tree 運算式樹 與 反射機制,封裝通用的 LinqHelper 類別,將 20 行排序邏輯精簡為 2 行呼叫。
// 原始碼:每新增一個可排序欄位就要修改此處
if (pager.order == "desc") {
switch (pager.sort) {
case "CreateTime": queryData = queryData.OrderByDescending(c => c.CreateTime); break;
case "Name": queryData = queryData.OrderByDescending(c => c.Name); break;
default: queryData = queryData.OrderByDescending(c => c.CreateTime); break;
}
} else {
switch (pager.sort) {
case "CreateTime": queryData = queryData.OrderBy(c => c.CreateTime); break;
case "Name": queryData = queryData.OrderBy(c => c.Name); break;
default: queryData = queryData.OrderBy(c => c.CreateTime); break;
}
}
缺點:違反開放封閉原則、維護成本高、易出現拼字錯誤、無法動態擴充。
在 App.Common 專案下建立 LinqHelper.cs,提供三大核心方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace App.Common {
public static class LinqHelper {
///
/// 動態排序:依欄位名稱與方向對 IQueryable 進行 OrderBy/OrderByDescending
///
public static IQueryable<T> DataSorting<T>(IQueryable<T> source, string sortExpression, string sortDirection) {
if (string.IsNullOrEmpty(sortExpression)) return source;
string sortingDir = sortDirection.ToUpper().Trim() == "ASC" ? "OrderBy" : "OrderByDescending";
// 建立參數運算式:x => x.PropertyName
ParameterExpression param = Expression.Parameter(typeof(T), "x");
PropertyInfo pi = typeof(T).GetProperty(sortExpression);
if (pi == null) throw new ArgumentException($"型別 {typeof(T).Name} 不包含屬性 {sortExpression}");
// 建立屬性存取運算式:x.PropertyName
Expression propertyAccess = Expression.Property(param, pi);
// 建立 Lambda:x => x.PropertyName
LambdaExpression lambda = Expression.Lambda(propertyAccess, param);
// 呼叫 Queryable.OrderBy/OrderByDescending 方法
MethodCallExpression expr = Expression.Call(
typeof(Queryable),
sortingDir,
new Type[] { typeof(T), pi.PropertyType },
source.Expression,
lambda
);
return source.Provider.CreateQuery<T>(expr);
}
///
/// 動態分頁:支援首頁與非首頁不同邏輯
///
public static IQueryable<T> DataPaging<T>(IQueryable<T> source, int pageNumber, int pageSize) {
if (pageNumber <= 1) return source.Take(pageSize);
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
}
///
/// 排序並分頁一鍵完成
///
public static IQueryable<T> SortingAndPaging<T>(IQueryable<T> source, string sortExpression, string sortDirection, int pageNumber, int pageSize) {
var query = DataSorting(source, sortExpression, sortDirection);
return DataPaging(query, pageNumber, pageSize);
}
}
}
xx.PropertyNamex => x.PropertyNameQueryable.OrderBy 或 OrderByDescending 靜態方法IQueryable<T>在 BLL 或 Repository 層中:
// 排序 + 分頁一行搞定
var pagedData = LinqHelper.SortingAndPaging<SysSampleModel>(
source: queryData,
sortExpression: pager.sort, // 來自 EasyUI DataGrid 的 sort 參數
sortDirection: pager.order, // 來自 EasyUI DataGrid 的 order 參數
pageNumber: pager.page, // 當前頁碼
pageSize: pager.rows // 每頁筆數
);
sort="CreateTime,Name" 格式,鏈式呼叫 ThenBy"Department.Name" 等導航屬性路徑StringComparer.OrdinalIgnoreCaseConcurrentDictionary 快取反射結果提升效能本篇同步修正了 GridPager 類別中的拼字錯誤:將屬性 sord 更正為 sort,解決後端無法接收 DataGrid 排序參數的 Bug。
感謝第 8 講留言區 @nyth 與 @紅顏醉丶帝 的建議啟發了此重構方向。