前一篇文章「練習題 - LINQ Single Column Dynamic Group」練習了各種動態單一欄位的 Group 操作,這一篇則是要來練習動態多欄位的 Group 操作。
使用環境:VS2013, ASP.NET MVC 5, EntityFramework 6.1.0
使用資料庫:Northwind
基本操作
首先來看看一般的情況下(非動態)是如何處理多欄位的 Group 操作。
下面的操作是使用 LINQ Method Syntax 的寫法,指定 SupplierID 與 CategoryID 來作為 Group 的 Key,
執行結果
T-SQL
接著再試試改用 LINQ Query Syntax 的方式來重新寫過上面的操作,
執行的頁面結果就跟上面的的一樣,所以就不顯示,以下直接顯示 T-SQL 內容,
使用 DynamicQuery(Dynamic Expression API)
看完以上的分別使用 LINQ Method Syntax 與 Query Syntax 的多欄位 Group 操作,其實病沒有使用動態多欄位的操作,這邊我必須坦承,其實對於要如何使用 LINQ 製作動態多欄位的 Group,我還真的沒有頭緒,網路上有很多的作法,但是都沒有讓我覺得容易理解與使用的方法,最後我還是選擇使用 DynamicQuery 來處理。
完整的程式碼
public ActionResult Index3()
{
db.Database.Log = (log) => Debug.WriteLine(log);
//it: Current Instance
var query = db.Products
.GroupBy("new(SupplierID,CategoryID)", "it", null)
.Select("new(Key.SupplierID as SupplierID, Key.CategoryID as CategoryID, Count() as Count)")
.OrderBy("SupplierID, CategoryID");
var message = new StringBuilder();
foreach (var result in query)
{
message.AppendLine(result + "</br>");
}
ViewBag.GroupResult = message.ToString();
return View();
}
執行結果
T-SQL
老實說,在 Controller 裡寫的那一段根本不算是動態(看看下面的程式),除非 GroupBy, Select, OrderBy 裡面的文字是用動態產生的,但我實在是不想去處理產生這些的動態文字,因為後續的變動性與維護性都不是很好。
public ActionResult Index3()
{
db.Database.Log = (log) => Debug.WriteLine(log);
//it: Current Instance
var query = db.Products
.GroupBy("new(SupplierID,CategoryID)", "it", null)
.Select("new(Key.SupplierID as SupplierID, Key.CategoryID as CategoryID, Count() as Count)")
.OrderBy("SupplierID, CategoryID");
var message = new StringBuilder();
foreach (var result in query)
{
message.AppendLine(result + "</br>");
}
ViewBag.GroupResult = message.ToString();
return View();
}
使用 GroupByMany 方法
twMVC 的夥伴「Bruce Chen 陳傳興」對於 LINQ 動態多欄位 Group 的處理提供了兩篇文章參考,
Playing with Linq grouping: GroupByMany ? – Mitsu’s blog- Site Home - MSDN Blogs
Linq GroupByMany dynamically – Mitsu’s blog - Site Home - MSDN Blogs
綜合以上兩篇文章的作法,我整理了以下的實作內容。
首先建立一個 GroupResult 物件,
再來針對 IEnumerable<GroupResult> 建立兩個擴充方法,
public static class MyEnumerableExtensions
{
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IQueryable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
var result = elements
.GroupBy(selector)
.Select(
g => new GroupResult
{
Key = g.Key,
Count = g.Count(),
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
return result;
}
else
{
return null;
}
}
public static IEnumerable<GroupResult> GroupByMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
if (groupSelectors.Length > 0)
{
var selector = groupSelectors.First();
//reduce the list recursively until zero
var nextSelectors = groupSelectors.Skip(1).ToArray();
var result = elements
.GroupBy(selector)
.Select(
g => new GroupResult
{
Key = g.Key,
Count = g.Count(),
Items = g,
SubGroups = g.GroupByMany(nextSelectors)
});
return result;
}
else
{
return null;
}
}
}
Controller 內的程式做法
View
執行結果
T-SQL
看到上面的 T-SQL 結果之後,我想大家應該都知道最後的這一個方法效能並不是很好,因為是將所有的資料從資料庫抓出來之後,再從系統裡去做 Group 的操作。
從上面一路看下來,可以得知,要使用 LINQ 去做到動態多欄位的 Group 操作還真的不好做耶。
或許是我能力不足、功力尚淺(簡言之就是嫩!)
也許是我還沒有碰到真的非得要作到 LINQ 做動態多欄位的 Group 操作,所以我無法了解為何一定要這麼做,如果說是因為要做報表或是一些統計彙整的功能,我真的建議就請 DBA 高手來幫我們處理這部份的需求或是建議我們應該要怎麼處理,至於用 LINQ 來處理這樣的需求…… 真的,程式開發人員的時間是有限的、是珍貴的,時間應該浪費在美好的事物上面。
以上
沒有留言:
張貼留言