本文將為大家介紹 LINQ 的分頁方法之標準查詢運算子:Take、Skip、TakeWhile、SkipWhile。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
Take 運算子可用來取出來源序列中,指定數量項目的資料,其簽名碼如下:
public static IEnumerable<TSource> Take<TSource>(
this IEnumerable<TSource> source,
int count
)
Take 運算子是擴充自 IEnumerable<TSource> 的方法,只需傳入一個 int 型別的參數,表示要取回的項目個數。當 Take 運算子被調用時,會從來源序列的第一個項目開始,依序取回指定數量的項目當做結果回傳。這裡有幾點特性要注意:
下面範例會取出來源序列中前 5 筆資料:
var list = new List<int>() {68,50,15,55,13,
0,81,29,40,10,
9,64,12,74,90,
25,4,77,53,42,
49,31,26};
var query = list.OrderBy(l => l).Take(5);
query.Dump();
/* 輸出:
0
4
9
10
12
*/
Skip 運算子和 Take 運算子正好相反,它可用來跳過來源序列中的前幾個項目,再把剩下的資料全部回傳。以下為 Skip 運算子的簽名碼:
public static IEnumerable<TSource> Skip<TSource>(
this IEnumerable<TSource> source,
int count
)
Skip 運算子的特性和 Take 雷同:
下面範例我們會跳過前 20 筆資料,只取回剩餘的項目:
var list = new List<int>() {68,50,15,55,13,
0,81,29,40,10,
9,64,12,74,90,
25,4,77,53,42,
49,31,26};
var query = list.OrderBy(l => l).Skip(20);
query.Dump();
/* 輸出:
77
81
90
*/
以上兩個運算子,互相搭配我們就可以做分頁查詢囉:
var list = new List<int>() {68,50,15,55,13,
0,81,29,40,10,
9,64,12,74,90,
25,4,77,53,42,
49,31,26};
var pageCnt = 2;
var pageRows = 5;
var query = list.Skip((pageCnt-1) * pageRows).Take(pageRows);
query.Dump();
/* 輸出:
0
81
29
40
10
*/
上述程式碼,我們建立一個有 23 個項目的清單,然後指定每頁筆數(pageRows) 5 筆,目前要查詢的頁數是(pageCnt)第 2 頁,接著定義 LINQ 查詢,利用 Skip 運算子跳過第 1 頁,然後用 Take 運算子取出第 2 頁所需的資料。
本範例為了方便大家對照,所以刻意不對資料來源做排序,且定義來源序列時,用 5 個項目為一列,所以大家應該可以發現這個輸出結果正好就是來源序列第 2 列的資料。
接著要說明 TakeWhile 和 SkipWhile 運算子,它們在我個人實務上挺少用的,因為 LINQ to Entities 和 LINQ to SQL 都不支援。
先說明 TakeWhile 運算子,以下為它的兩個多載方法簽名碼:
public static IEnumerable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
public static IEnumerable<TSource> TakeWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate
)
兩個多載方法都是接收一個委派參數,回傳 bool 值。兩者的差別是第二個多載方法之委派參數,會多傳入一個 int 型別的參數,它表示來源項目的索引值。
TakeWhile 運算子,在取資料前,會先把來源序列的項目丟給委派參數 predicate 並判斷回傳值為 true 時,才取出來,要注意它的特性是:一旦 predicate 回傳 false,就會中止查詢作業,回傳到目前為止的處理結果:
int[] numbers = { 33, 60, 222, 34, 999, 1 };
numbers.TakeWhile(n => n < 100).Dump("TakeWhile");
/* 輸出:
33
60
*/
上述程式碼,我們設定一個來源序列後,定為 LINQ 查詢,使用 TakeWhile 運算指,指定 predicate 的邏輯是小於 100 的數字。我們對照來源序列,會發現總共有 4 個項目符合,分別是第 1、2、4、6 個項目,但是輸出結果只有前兩個項目,其原因就是因為當來源序列跑到第 3 個項目「222」時,predicate 會回傳 false,這時候就會中止剩下的查詢作業,直接回傳到目前為止的處理結果,也就是前兩個項目。
最後說明 SkipWhile 運算子,以下為多載方法的簽名碼:
public static IEnumerable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
public static IEnumerable<TSource> SkipWhile<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate
)
SkipWhile 運算子的多載方法和 TakeWhile 運算子雷同,都是兩個,而且差別也只是委派方法 predicate 傳入的參數多一個 int 型別的來源項目索引值,但是它們兩個的用途正好相反,使用時要先想清楚才不會得到非預期的結果。SkipWhile 運算子,會依序把來源序列的每個項目傳給 predicate 委派判斷,若回傳 true,就跳過,一直到 predicate 回傳 true 時,就會把剩餘的來源序列之項目全部回傳:
int[] numbers = { 33, 60, 222, 34, 999, 1 };
numbers.SkipWhile((n, idx) => n < 100 || n > (idx * 200)).Dump("SkipWhile");
/* 輸出:
222
34
999
1
*/
上述程式碼,我們應用 SkipWhile 第二個多載方法,也就是多傳為一個來源項目索引值給 predicate 委派,而 predicate 的邏輯是,只要傳入項目小於 100,或者大於索引值 * 200,都跳過。最後輸出結果是跳過前兩個項目,因為第三個項目 222 大於 100,造成 predicate 回傳 false,所以系統就會把剩餘的來源序列中之項目統統回傳。
我個人學習時,在 SkipWhile 卡了一陣子,因為一直不能很順的理解它的邏輯,所以希望我上述的說明會讓大家更容易理解,省去我自己學習時的困擾。