AppBox 是基於 FineUI 的通用權限管理框架,包括用戶管理、職稱管理、部門管理、角色管理、角色權限管理等模塊。
Entity Framework提供的排序功能
再來回顧一下上篇文章,加載用戶列表並進行排序數據庫分頁的代碼:
var q = DB.Users.Include(u => u.Dept);
// 在用戶名稱中搜索
string searchText = ttbSearchMessage.Text.Trim();
if (!String.IsNullOrEmpty(searchText))
{
q = q.Where(u => u.Name.Contains(searchText) || u.ChineseName.Contains(searchText) || u.EnglishName.Contains(searchText));
}
// 過濾啟用狀態
if (rblEnableStatus.SelectedValue != "all")
{
q = q.Where(u => u.Enabled == (rblEnableStatus.SelectedValue == "enabled" ? true : false));
}
// 在查詢添加之后,排序和分頁之前獲取總記錄數
Grid1.RecordCount = q.Count();
// 排列
q = q.OrderBy(u => u.Name);
// 數據庫分頁
q = q.Skip(Grid1.PageIndex * Grid1.PageSize).Take(Grid1.PageSize);
Grid1.DataSource = q;
Grid1.DataBind();
讓我們把關注點集中到排序代碼上:
q = q.OrderBy(u => u.Name);
在FineUI實際應用中,我們一般是從表格的 SortField 中讀取排序字段,顯然EF提供的OrderBy無法接受字符串表示的排序字段。
手工創建Lamba表達式
通過搜索發現了這個帖子:http://stackoverflow.com/questions/10072250/generic-funct-k-to-sort-collections-of-different-types/10074873
據此我們可以寫出如下的代碼:
public Expression<Func<T, To>> GetSortExpression<T, To>(String sortBy)
{
var param = Expression.Parameter(typeof(T), "x");
Expression expr = Expression.Property(param, sortBy);
return Expression.Lambda<Func<T, To>>(expr, param);
}
protected IQueryable<T> Sort<T>(IQueryable<T> q, FineUI.Grid grid)
{
string sortField = grid.SortField;
if (grid.SortDirection == "ASC")
{
q = q.OrderBy(GetSortExpression<T, object>(sortField));
}
else
{
q = q.OrderByDescending(GetSortExpression<T, object>(sortField));
}
return q;
}
經過測試,我們發現這個方法不支持bool, int, DateTime, DateTime?類型的列排序。
經過擴展后的代碼如下所示:
protected IQueryable<T> Sort<T>(IQueryable<T> q, FineUI.Grid grid)
{
string sortField = grid.SortField;
var propertyType = typeof(T).GetProperty(sortField).PropertyType;
if (grid.SortDirection == "ASC")
{
if (propertyType == typeof(bool))
{
q = q.OrderBy(GetSortExpression<T, bool>(sortField));
}
else if (propertyType == typeof(int))
{
q = q.OrderBy(GetSortExpression<T, int>(sortField));
}
else if (propertyType == typeof(DateTime))
{
q = q.OrderBy(GetSortExpression<T, DateTime>(sortField));
}
else if (propertyType == typeof(DateTime?))
{
q = q.OrderBy(GetSortExpression<T, DateTime?>(sortField));
}
else
{
q = q.OrderBy(GetSortExpression<T, object>(sortField));
}
}
else
{
if (propertyType == typeof(bool))
{
q = q.OrderByDescending(GetSortExpression<T, bool>(sortField));
}
else if (propertyType == typeof(int))
{
q = q.OrderByDescending(GetSortExpression<T, int>(sortField));
}
else if (propertyType == typeof(DateTime))
{
q = q.OrderByDescending(GetSortExpression<T, DateTime>(sortField));
}
else if (propertyType == typeof(DateTime?))
{
q = q.OrderByDescending(GetSortExpression<T, DateTime?>(sortField));
}
else
{
q = q.OrderByDescending(GetSortExpression<T, object>(sortField));
}
}
return q;
}
但這種做法過於臃腫,有沒有更好的辦法呢?
更好的SortBy擴展方法
后來,我們發現了這篇文章:http://stackoverflow.com/questions/3945645/sorting-gridview-with-entity-framework
通過對 IQueryable<T> 進行擴展,提供了接受類似 "Name DESC", "CreateTime", "CreateTime DESC" 參數的 SortBy 方法,更具有通用性。
原始的SortBy擴展方法:
public static class QueryExtensions {
public static IQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName) {
if (source == null) {
throw new ArgumentNullException("source");
}
// DataSource control passes the sort parameter with a direction
// if the direction is descending
int descIndex = propertyName.IndexOf(" DESC");
if (descIndex >= 0) {
propertyName = propertyName.Substring(0, descIndex).Trim();
}
if (String.IsNullOrEmpty(propertyName)) {
return source;
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
MemberExpression property = Expression.Property(parameter, propertyName);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = (descIndex < 0) ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
}
不過這個方法不支持"Name ASC"形式的參數,所以我們進行了簡單的修正,修正后的SortBy擴展方法:
public static class QueryExtensions
{
public static IQueryable<T> SortBy<T>(this IQueryable<T> source, string sortExpression)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
string sortDirection = String.Empty;
string propertyName = String.Empty;
sortExpression = sortExpression.Trim();
int spaceIndex = sortExpression.Trim().IndexOf(" ");
if (spaceIndex < 0)
{
propertyName = sortExpression;
sortDirection = "ASC";
}
else
{
propertyName = sortExpression.Substring(0, spaceIndex);
sortDirection = sortExpression.Substring(spaceIndex + 1).Trim();
}
if (String.IsNullOrEmpty(propertyName))
{
return source;
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
MemberExpression property = Expression.Property(parameter, propertyName);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = (sortDirection == "ASC") ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
}
優化后的排序分頁代碼
首先在頁面基類PageBase中定義排序和分頁的代碼(使用了前面定義的 SortBy 擴展函數):
protected IQueryable<T> Sort<T>(IQueryable<T> q, FineUI.Grid grid)
{
return q.SortBy(grid.SortField + " " + grid.SortDirection);
}
protected IQueryable<T> SortAndPage<T>(IQueryable<T> q, FineUI.Grid grid)
{
return Sort(q, grid).Skip(grid.PageIndex * grid.PageSize).Take(grid.PageSize);
}
最終查詢用戶列表的代碼:
var q = DB.Users.Include(u => u.Dept);
// 在用戶名稱中搜索
string searchText = ttbSearchMessage.Text.Trim();
if (!String.IsNullOrEmpty(searchText))
{
q = q.Where(u => u.Name.Contains(searchText) || u.ChineseName.Contains(searchText) || u.EnglishName.Contains(searchText));
}
// 過濾啟用狀態
if (rblEnableStatus.SelectedValue != "all")
{
q = q.Where(u => u.Enabled == (rblEnableStatus.SelectedValue == "enabled" ? true : false));
}
// 在查詢添加之后,排序和分頁之前獲取總記錄數
Grid1.RecordCount = q.Count();
// 排列和數據庫分頁
q = SortAndPage<User>(q, Grid1);
Grid1.DataSource = q;
Grid1.DataBind();
下載或捐贈AppBox
1. AppBox v2.1 是免費軟件,免費提供下載:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3788
2. AppBox v3.0 是捐贈軟件,你可以通過捐贈作者來獲取AppBox v3.0的全部源代碼(http://fineui.com/donate/)。
