C#學習筆記(八)—–LINQ查詢創建策略

這篇文章是引用博客園的,這篇文章應該也是出自C# in a nutshell,但是翻譯的比較好,容易理解。地址爲:http://www.cnblogs.com/lifepoem/archive/2011/10/31/2230175.html

LINQ查詢創建策略

通過前面幾篇的討論學習,我們已經瞭解了怎麼去寫一個比較簡單的LINQ查詢,也知道了創建LINQ查詢的兩種方式:方法語法和查詢表達式。在這裏,我們會描述三種創建複雜LINQ查詢的創建策略:

漸進式創建查詢

漸進式創建查詢就是通過鏈接查詢運算符的方式來創建LINQ查詢。因爲每一個查詢運算符返回一個裝飾者sequence,所以我們可以在其之上繼續調用其它查詢運算符。使用這種方式有如下幾個優點:

  • 使得查詢易於編寫
  • 我們可以根據條件來決定是否調用某個查詢運算符,如:if (includeFilter) query = query.Where(…)
    漸進的方式通常是對查詢的創建有益的,考慮如下的例子:我們需要在名字列表中去除所有名字的元音字母,然後對長度大於2的名字進行排序。在方法語法中,我們可以在一個表達式中完成這個查詢:
 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
            IEnumerable<string> query = names
                .Select(n => n.Replace("a", "").Replace("e", "").Replace("i", "")
                    .Replace("o", "").Replace("u", ""))
                .Where(n => n.Length > 2)
                .OrderBy(n => n);   // Result: Dck, Hrry, Mry

如果直接將上面的query改寫成查詢表達式語法,我們將會遇到麻煩,這時因爲查詢表達式要求要以Select或Group結束。但在上面的查詢中,我們需要先做Select(結果投影)去除元音字母,再做過濾和排序。如果把Select直接放到後面,那麼結果將會被改變。幸運的是,我們還是有辦法讓查詢表達式來完成上面的工作,得到我們期望的結果。 第一種方式就是查詢表達式的漸進式(分步)查詢:

IEnumerable<string> query =
                from n in names
                select n.Replace("a", "").Replace("e", "").Replace("i", "")
                    .Replace("o", "").Replace("u", "");

            query = from n in query
                    where n.Length > 2
                    orderby n
                    select n;   // Result: Dck, Hrry, Mry

into關鍵字

在我們前面查詢表達式的例子中,select關鍵字的出現也就意味着查詢的結束了。而into關鍵字讓我們在結果投影之後還可以繼續我們的查詢,它是對分步構建查詢表達式的一種簡寫方式。現在我們可以使用into關鍵字來重寫上例中的查詢:

IEnumerable<string> query =
                    from n in names
                    select n.Replace("a", "").Replace("e", "").Replace("i", "")
                            .Replace("o", "").Replace("u", "")
                    into noVowel
                    where noVowel.Length > 2
                    orderby noVowel
                    select noVowel;   // Result: Dck, Hrry, Mry

我們只能在select和group子句後面使用into關鍵字,它會重新開始一個查詢,讓我們可以繼續引入where, orderby和select子句。儘管表面上看,我們重新創建了一個新的查詢,但當上面的查詢被翻譯成方法語法時,它只是一個查詢,一個鏈接了多個運算符的查詢,所以上面的寫法不會造成性能問題。

需要注意的是,所有的查詢變量在into關鍵字之後都不再可見,下面的例子就說明了這一點:

 var query =
                from n1 in names
                select n1.ToUpper()
                into n2                     //into之後只有n2可見
                    where n1.Contains("x")  //Error: n1不可見
                    select n2;

要理解其原因,我們只要看看它編譯器爲它翻譯成對應的方法語法就能知曉:

 var query = names
                .Select(n1 => n1.ToUpper())
                .Where(n2 => n1.Contains("x")); //Error: n1不再可見,lambda表達式中只有n2

包裝查詢

漸進式查詢創建方式可以通過在一個查詢中嵌入另一個查詢來改寫,這樣可以把多個查詢組合成單個查詢,即:

var tempQuery = tempQueryExpr

var finalQuery = from … in (tempQuery)

可以改寫爲:

var query = from … in (tempQueryExpr)

上面兩種方式以及into關鍵字的工作方式是一樣的,編譯器都會把他們翻譯成一個鏈接查詢運算符。請看下面的示例:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            // 漸進式查詢(Progressive query building)
            IEnumerable<string> query =
                from n in names
                select Regex.Replace(n, "[aeiou]", "");

            query = from n in query where n.Length > 2 orderby n select n;

            // 用包裝查詢方式進行改寫(Wrapping Queries)
            IEnumerable<string> query2 =
                from n1 in
                    (
                        from n2 in names
                        select Regex.Replace(n2, "[aeiou]", "")
                    )
                where n1.Length > 2
                orderby n1
                select n1;

            // 與上面等價的方法語法
            IEnumerable<string> query3 = names
                .Select(n => Regex.Replace(n, "[aeiou]", ""))
                .Where(n => n.Length > 2)
                .OrderBy(n => n);

數據轉換

LINQ中的數據轉換,也叫結果投影,是指LINQ查詢select的輸出。到目前爲止,我們還只是看到了輸出單個標量元素的示例。通過使用對象初始化器,我們可以輸出更爲複雜的結果類型。比如下面的示例,當我們在把姓名中的元音字母去掉之後,我還需要保存姓名的原始版本:

  class TempProjectionItem
        {
            public string Original;
            public string Vowelless;
        }

        static void TestProjectionStrategy()
        {
            string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

            IEnumerable<TempProjectionItem> temp =
                from n in names
                select new TempProjectionItem
                {
                    Original = n,
                    Vowelless = Regex.Replace(n, "[aeiou]", "")
                };
            //我們可以繼續在結果中查詢
            IEnumerable<string> query =
                from item in temp
                where item.Vowelless.Length > 2 //按去除元音字母版本過濾
                select item.Original;           //結果爲姓名原始版本
        }

匿名類型

上面我們自己定義了類型TempProjectionItem來存放查詢的結果。通過使用匿名類型,我們可以省去這種中間類型的定義,而由編譯器來幫我們完成:

 var intermediate = from n in names
                               select new
                               {
                                   Original = n,
                                   Vowelless = Regex.Replace(n, "[aeiou]", "")
                               };
            IEnumerable<string> query = from item in intermediate
                                        where item.Vowelless.Length > 2
                                        select item.Original;

需要注意的是,因爲匿名類型的確切類型名是由編譯器自動產生的,因此intermediate的類型爲:IEnumerable 。我們來聲明這種類型的唯一方式就是使用var關鍵字,這時,var不只是更加簡潔,而且也是必需的手段。

let關鍵字

let關鍵字讓我們可以在保持範圍變量的同時引入新的查詢變量。比如上面的示例,我們可以用let關鍵字作如下改寫:

 string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
            var query = from n in names
                        let Vowelless = Regex.Replace(n, "[aeiou]", "")
                        where Vowelless.Length > 2
                        select n;   //正是因爲使用了let,此時n仍然可見

let關鍵字非常靈活和方便,就像例子看到的那樣。而且,我們可以使用多個let關鍵字,並且後面的 let表達式可以引用前一個let關鍵字引入的變量。
本系列LINQ之路文章到此已經對LINQ to Objects進行了比較詳細的討論,接下去的打算是對解釋查詢(LINQ to SQL, LINQ to XML、entity framework等)以及更多的查詢運算符進行討論和學習。希望系列文章能對閱者有些幫助,也期待大家的意見和提議^_^。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章