上一篇「ASP.NET MVC 使用 jQUery EasyUI DataGrid - 排序 (Sorting)」說明了如何讓 DataGrid 加入資料排序的功能,而 jQuery EasyUI 1.3.4 有提供了一個新的屬性「multiSort」,這個屬性可以讓 DataGrid 有多欄排序的功能,之前的排序都是針對某一個欄位做排序顯示,而多欄排序是可以同時選擇多個不同欄位,而且欄位的排序順序可以不同,這一篇文章就來說明要如何處理多欄排序的操作。
要讓 jQuery EasyUI DataGrid 有多欄排序的功能,在 DataGrid 的設定裡增加「multiSort」屬性然後設定為 true,
增加了「multiSort: true」屬性設定後,我們就可以選擇多個欄位做資料的排序,
觀察 POST 分頁與排序資料到後端 Controller 的內容,如下:
可以看到 order 與 sort 將有選擇排序的欄位與順序以逗點分隔的格式將資料傳送到 Controller,不過我們還沒有對 CustomerService 的程式作修改,而因應多欄排序的需求,將對原本的程式作適度的調整。
Step.1
多欄排序的操作是不必修改 CustomerController 的程式內容,但是需要對 CustomerService 的 GetJsonForGrid method 的程式做修改,排序欄位名稱與排序順序是依照先後順序成對排列的,所以我們需要動態的去增加 OrderBy 與 ThenBy 的 LINQ Expression,另外原本為了防止傳進來的 propertyName 與 oder 資料有錯而增加的資料判斷就必須做修改。
所以我們要先來解決 PropertyName 與其對應的 oder 成對的部分,在 EntityHelper.cs 裡增加了 GetPropertySortTuples<T> 方法,
/// <summary>
/// Gets the property sort tuples.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName">Name of the property.</param>
/// <param name="order">The order.</param>
/// <returns></returns>
public static List<Tuple<string, string>> GetPropertySortTuples<T>(
string propertyName,
string order)
{
var result = new List<Tuple<string, string>>();
//取得 Entity 所有的 Property 名稱
var entityPropertyNames = EntityHelper.EntityPropertyNames<T>();
var propertyNameOptions = propertyName.Split(',').ToList();
var orderOptions = order.Split(',').ToList();
for (int i = 0; i < propertyNameOptions.Count; i++)
{
var columnName = propertyNameOptions[i].Trim();
var sortOrder = string.IsNullOrWhiteSpace(orderOptions[i]) ? "asc" : orderOptions[i];
var propertyNames = entityPropertyNames as string[] ?? entityPropertyNames.ToArray();
if (!propertyNames.Contains(columnName))
{
columnName = string.Empty;
}
if (!(sortOrder.Equals("asc", StringComparison.OrdinalIgnoreCase)
|| sortOrder.Equals("desc", StringComparison.OrdinalIgnoreCase)))
{
order = "asc";
}
if (!string.IsNullOrEmpty(columnName))
{
result.Add(new Tuple<string, string>(columnName, sortOrder));
}
}
return result;
}
如果有多個排序欄位時,所得到的結果就會如下圖裡的內容,
Step.2
解決了多個排序欄位的 Property 與 Sort Order 對應之後,接著就是要去使用這個資料來去動態組合 LINQ Expression 的 Sort Expresion 部分。
之前我們使用了 Dynamic Expression API 來解決單一欄位排序的動態 Sort Expression 需求,如下:
正當我想要用下面的方式來動態組合 Sort Expression 部份的時候,卻看到 Dynamic Expression API 沒有提供 ThenBy 方法,
沒有支援 ThenBy 的處理就問題大了,因為多欄位排序一定要使用到 ThenBy,第一個欄位使用 OrderBy 或 OrderByDescending,那麼接續的排序就需要用到 ThenBy 或 ThenByDescending,不然多欄位排序的功能就無法實做出來,所以這邊我們就無法繼續使用 Dynamic Expression API 來完成,所以必須要另外想辦法。
註:
Dynamic Expression API 不是不支援多重排序,而是沒有提供 ThenBy 與 ThenByDescending 的方法,而至於要如何使用 Dynamic Expression API 操作都多欄位排序的處理,在之後的文章裡將會做說明。
我還在寫這篇文章的時候(這一篇寫了三天),我正當煩惱無法用比較清楚的方式來說明有關 OrderBy 與 ThenBy 的時候,微軟 MVP – 91 就發佈了一篇有關 OrderBy, ThenBy 的文章:
[.NET]快快樂樂學LINQ系列-OrderBy(), ThenBy() 簡介
此文章裡詳述了 IOrderedEnumerable<T>, OrderBy, ThenBy 以及多重排序的內容,所以請各位務必先看過 91 哥的文章,如此後續的操作才不致於看得模模糊糊的。
Step.3
確定無法使用 Dynamic Expression API 來解決多欄位排序的需求後,就直接從 LINQ Expression 下手,而我們先看 MSDN 裡 IOrderedEnumerable<T> 的說明:
MSDN - IOrderedEnumerable(TElement) 介面 (System.Linq)
摘錄其中的說明:
擴充方法 ThenBy 和 ThenByDescending 會在 IOrderedEnumerable<TElement> 型別物件上操作。 藉由呼叫其中一個主要排序方法 (OrderBy 或 OrderByDescending),傳回 IOrderedEnumerable<TElement>,即可取得 IOrderedEnumerable<TElement> 型別物件。 而 ThenBy 和 ThenByDescending 次要排序方法會傳回 IOrderedEnumerable<TElement> 型別物件。 這個設計允許對 ThenBy 或 ThenByDescending 進行任意數目的連續呼叫,而每個呼叫都會在上一個呼叫所傳回的排序資料上執行次要排序。
所以我們要把 Step.1 裡從 EntityHelper.GetPropertySortTuples<T>() 所取得的 propertySortTuples 去想辦法產生 IOrderedEnumerable<T> 的結果,而這一部份的程式我是取用「Sorting in IQueryable using string as column name | Jiří {x2} Činčura」這一篇文章裡的作法,稍做調整後的程式如下:
IOrderedQueryableHelper.cs
public static class IOrderedQueryableHelper
{
/// <summary>
/// Orders the by.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source,
string propertyName)
{
return OrderingHelper(source, propertyName, false, false);
}
/// <summary>
/// Orders the by descending.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source,
string propertyName)
{
return OrderingHelper(source, propertyName, true, false);
}
/// <summary>
/// Thens the by.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source,
string propertyName)
{
return OrderingHelper(source, propertyName, false, true);
}
/// <summary>
/// Thens the by descending.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source,
string propertyName)
{
return OrderingHelper(source, propertyName, true, true);
}
/// <summary>
/// Orderings the helper.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="propertyName">Name of the property.</param>
/// <param name="descending">if set to <c>true</c> [descending].</param>
/// <param name="anotherLevel">if set to <c>true</c> [another level].</param>
/// <returns></returns>
private static IOrderedQueryable<T> OrderingHelper<T>(IQueryable<T> source,
string propertyName,
bool descending,
bool anotherLevel)
{
ParameterExpression param = Expression.Parameter(typeof(T), string.Empty);
MemberExpression property = Expression.PropertyOrField(param, propertyName);
LambdaExpression sort = Expression.Lambda(property, param);
MethodCallExpression call = Expression.Call(
typeof (Queryable),
string.Concat((!anotherLevel ? "OrderBy" : "ThenBy"), (descending ? "Descending" : string.Empty)),
new[] {typeof (T), property.Type},
source.Expression,
Expression.Quote(sort));
return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(call);
}
}
Step.4
建立好 IOrderedQueryableHelper.cs 之後,我們就可以在 CustomerService 的 GetJsonForGrid 方法裡使用,如下:
CustomerService 的 GetJsonForGrid 方法的完整程式如下:
public JArray GetJsonForGrid(
int page = 1,
int pageSize = 10,
string propertyName = "CustomerID",
string order = "asc")
{
// 取得多個排序欄位與順序的 Tuple 結果
var propertySortTuples =
EntityHelper.GetPropertySortTuples<Customer>(propertyName, order);
JArray ja = new JArray();
var query = db.Customers.AsQueryable();
IOrderedQueryable<Customer> orderQuery = null;
for (int index = 0; index < propertySortTuples.Count; index++)
{
if (propertySortTuples[index].Item2.Equals("asc", StringComparison.OrdinalIgnoreCase))
{
orderQuery = index.Equals(0)
? query.OrderBy<Customer>(propertySortTuples[index].Item1)
: orderQuery.ThenBy<Customer>(propertySortTuples[index].Item1);
}
else
{
orderQuery = index.Equals(0)
? query.OrderByDescending<Customer>(propertySortTuples[index].Item1)
: orderQuery.ThenByDescending<Customer>(propertySortTuples[index].Item1);
}
}
query = orderQuery;
query = query.Skip((page - 1) * pageSize).Take(pageSize);
foreach (var item in query)
{
var itemObject = new JObject
{
{"CustomerID", item.CustomerID},
{"CompanyName", item.CompanyName},
{"ContactName", item.ContactName},
{"ContactTitle", item.ContactTitle},
{"Address", item.Address},
{"City", item.City},
{"Region", item.Region},
{"PostalCode", item.PostalCode},
{"Country", item.Country},
{"Phone", item.Phone},
{"Fax", item.Fax}
};
ja.Add(itemObject);
}
return ja;
}
執行結果:
首先只選擇 Country 欄位進行排序,
然後再選擇 City 欄位進行排序,
再點選 City 欄位以進行降冪排序,可以看到多重排序的結果,先對 Country 做升冪排序然後再做 City 的降冪排序,
從 EF 所產出的 SQL Command 就可以清楚看到確實有進行多個欄位的且順序不同的排序,
SELECT TOP (10)
[Extent1].[CustomerID] AS [CustomerID],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[ContactName] AS [ContactName],
[Extent1].[ContactTitle] AS [ContactTitle],
[Extent1].[Address] AS [Address],
[Extent1].[City] AS [City],
[Extent1].[Region] AS [Region],
[Extent1].[PostalCode] AS [PostalCode],
[Extent1].[Country] AS [Country],
[Extent1].[Phone] AS [Phone],
[Extent1].[Fax] AS [Fax]
FROM ( SELECT [Extent1].[CustomerID] AS [CustomerID], [Extent1].[CompanyName] AS [CompanyName], [Extent1].[ContactName] AS [ContactName], [Extent1].[ContactTitle] AS [ContactTitle], [Extent1].[Address] AS [Address], [Extent1].[City] AS [City], [Extent1].[Region] AS [Region], [Extent1].[PostalCode] AS [PostalCode], [Extent1].[Country] AS [Country], [Extent1].[Phone] AS [Phone], [Extent1].[Fax] AS [Fax], row_number() OVER (ORDER BY [Extent1].[Country] ASC, [Extent1].[City] DESC) AS [row_number]
FROM [dbo].[Customers] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 10
ORDER BY [Extent1].[Country] ASC, [Extent1].[City] DESC
操作動畫:
好像到這邊就講完了多欄位排序的處理,有需要分成兩篇文章嗎?
其實還有後續的處理以及程式的修改,為避免文章過長,就拆成兩篇做說明,基本上多欄位排序的功能已經完成了。
參考連結:
MSDN - IOrderedEnumerable(TElement) 介面 (System.Linq)
[.NET]快快樂樂學LINQ系列-OrderBy(), ThenBy() 簡介 - In 91- 點部落
Sorting in IQueryable using string as column name | Jiří {x2} Činčura
Multiple Field Sorting by Field Names Using Linq - CodeProject
以上
沒有留言:
張貼留言