有3種策略可以應用於創建更加復雜的查詢:
逐步創建 /使用into關鍵字/包裝多個查詢
逐步創建
之前我們曾演示過如何逐步的創建一個Lambda查詢
1: string[] players = { "Tim Ducan", "Lebrom James", "Kobe Byrant" };
2: var filtered = players.Where (n => n.Contains ("a"));
3: var sorted = filtered.OrderBy (n => n);
4: var query = sorted.Select (n => n.ToUpper());
因為每一個參與的查詢操作符都返回一個裝飾序列, 其結果和你使用的單一的鏈式或多層裝飾器的查詢結果是一致的. 這裡有幾個潛在的好處, 如果你逐步的創建查詢的話:
1. 查詢更加簡單容易編寫
2. 可以有條件的添加查詢操作符
在復合查詢中使用逐步構造通常是很有用的做法. 為了演示, 假設我們想要使用正則表達式將所有的元音從一系列的名字中移除, 然後按字母排序並列出那些長度仍然大於2的名字. 使用Lambda語法, 我們可以使用一個單一的表達式完成:
1: string[] names = { "James","Jack","Landy","C.Y","Jay" };
2: IEnumerable<string> query = names
3: .Select (n => Regex.Replace (n, "[aeiou]", ""))
4: .Where (n => n.Length > 2)
5: .OrderBy (n => n);
6:
7: foreach(var name in query)
8: {
9: Console.WriteLine(name); //C.Y,Jck,Jms,Lndy
10: }
如果我們將它直接翻譯成復合查詢語法的話會碰到一些問題, 因為復合查詢語句必須按照where-orderby-select的順序出現編譯器才能正確編譯. 但是如果我們簡單的按這個要求重新排列的話, 結果將會不一樣:
1: string[] names = { "James","Jack","Landy","C.Y","Jay" }; IEnumerable<string> query =
2: from n in names
3: where n.Length > 2
4: orderby n
5: select Regex.Replace (n, "[aeiou]", "");
6:
7: foreach(var name in query)
8: {
9: Console.WriteLine(name); //C.Y,Jck,Jms,Jy,Lndy
10: }
幸運的是使用復合查詢語法我們還可以有很多的方法可以得到跟上述查詢一致的結果. 這其中的第一種就是使用逐步構造查詢:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "");
4:
5: query = from n in query
6: where n.Length > 2
7: orderby n
8: select n;
9:
10: foreach(var name in query)
11: {
12: Console.WriteLine(name); //C.Y,Jck,Jms,Lndy
13: }
into關鍵字
into關鍵字可以讓查詢在經過一系列處理之後繼續進行, 並且是逐步構造查詢的快捷方式.使用into, 我們可以將上述的查詢改寫為:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "")
4: into noVowel
5: where noVowel.Length > 2
6: orderby noVowel
7: select noVowel;
唯一可以使用into的地方是在select或者group語句之後. Into”重新開始”一個新的查詢, 並且允許你繼續使用全新的where, orderby 和select語句.
作用域規則
所有的查詢變量在into關鍵字之後都不再屬於自己的作用范圍, 例如下面的查詢將不會被編譯通過:
1: var query =
2: from n1 in names
3: select n1.ToUpper()
4: into n2
5: where n1.Contains ("x") // 非法, n1超出了作用域
6: select n2;
要了解為什麼, 考慮一下將上述查詢轉換成Lamdba語法之後:
1: var query = names
2: .Select (n1 => n1.ToUpper())
3: .Where (n2 => n1.Contains ("x"));
當where運行的時候原始的名字(n1)已經丟失了, where操作符的輸入序列只包含了大寫的名字, 因此不能根據n1來過濾了.
包裝查詢
一個漸進查詢可以被改造成一個包裝查詢, 如下例:
var tempQuery = tempQueryExpr
var finalQuery = from … in tempQuery…
也可以進一步改造為:
var finalQuery = from … in (tempQueryExpr)
包裝查詢從語義上等同於漸進式查詢或者使用into關鍵字(沒有中間變量). 最終結果就是只有一行包含多個操作符的查詢語句. 例如, 考慮下面的查詢:
1: IEnumerable<string> query =
2: from n in names
3: select Regex.Replace (n, "[aeiou]", "");
4:
5: query = from n in query
6: where n.Length < 2
7: orderby n
8: select n;
包裝改造以後:
1: IEnumerable<string> query =
2: from n1 in
3: (
4: from n2 in names
5: select Regex.Replace (n2, "[aeiou]", "")
6: )
7: where n1.Length > 2 orderby n1 select n1;
當我們將它裝成Lambda語法, 結果也是只有一行包含多個查詢操作符的語句:
1: IEnumerable<string> query = names
2: .Select (n => Regex.Replace (n,"[aeiou]", ""))
3: .Where (n => n.Length > 2)
4: .OrderBy (n => n);
(編譯器實際上不會執行最後的.Select(n=>n),因為它是多余的)
包裝查詢有時候可能會和我們之前談過的子查詢有點混淆, 因為它們之間很相似: 都有相似的inner和outer查詢. 然而轉換成Lambda語法的時候, 你會發現包裝查詢就是簡單對於順序鏈式操作符的包裝(轉換成Lambda之後操作符順序跟包裝查詢的順序完全一樣). 而子查詢並不是這樣的, 它使用Lambda表達式將一個inner查詢嵌入到另外一個當中. 待續!