[C#] 走進 LINQ 的世界

走進 LINQ 的世界

  在此以前曾發表過三篇關於 LINQ 的隨筆:html

    進階:《LINQ 標準查詢操做概述(強烈推薦)數據庫

    技巧:《Linq To Objects - 如何操做字符串》 和 《Linq To Objects - 如何操做文件目錄編程

  如今,本身打算再整理一篇關於 LINQ 入門的隨筆,也是圖文並茂的哦。數組

 

目錄

 

LINQ 簡介

  語言集成查詢 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一項創新功能。緩存

  傳統上,針對數據的查詢都是以簡單的字符串表示,而沒有編譯時類型檢查或 IntelliSense 支持。此外,您還必須針對如下各類數據源學習一種不一樣的查詢語言:SQL 數據庫、XML 文檔、各類 Web 服務等等。 經過LINQ, 您可使用語言關鍵字和熟悉的運算符針對強類型化對象集合編寫查詢。編程語言

  

  在 Visual Studio 中,能夠爲如下數據源編寫 LINQ 查詢:SQL Server 數據庫、XML 文檔、ADO.NET 數據集,以及支持  IEnumerable 或泛型  IEnumerable<T> 接口的任意對象集合。
  使用要求:項目 ≥ .NET Framework 3.5 。
 

1、介紹 LINQ 查詢

  查詢是一種從數據源檢索數據的表達式。隨着時間的推移,人們已經爲各類數據源開發了不一樣的語言;例如,用於關係數據庫的 SQL 和用於 XML 的 XQuery。所以,開發人員不得不針對他們必須支持的每種數據源或數據格式而學習新的查詢語言。LINQ 經過提供一種跨數據源和數據格式使用數據的一致模型,簡化了這一狀況。在 LINQ 查詢中,始終會用到對象。可使用相同的編碼模式來查詢和轉換 XML 文檔、SQL 數據庫、ADO.NET 數據集、.NET 集合中的數據以及對其有 LINQ 提供程序可用的任何其餘格式的數據。  ide

 

  1.1 查詢操做的三個部分

  操做三部曲:①取數據源 ②建立查詢 ③執行查詢函數

 1 internal class Program
 2 {
 3         private static void Main(string[] args)
 4         {
 5             //1.獲取數據源
 6             var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
 7 
 8             //2.建立查詢
 9             var numQuery =
10                 from num in nums
11                 where (num % 2) == 0
12                 select num;
13 
14             //3.執行查詢
15             foreach (var num in numQuery)
16             {
17                 Console.WriteLine("{0}", num);
18             }
19         }
20 }
View Code

 

   下圖顯示了完整的查詢操做。在 LINQ 中,查詢的執行與查詢自己大相徑庭;換句話說,查詢自己指的是隻建立查詢變量,不檢索任何數據。工具

  

  1.2 數據源

  在上一個示例中,因爲數據源是數組,所以它隱式支持泛型 IEnumerable<T> 接口。支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的類型稱爲可查詢類型。  post

  可查詢類型不須要進行修改或特殊處理就能夠用做 LINQ 數據源。若是源數據尚未做爲可查詢類型出如今內存中,則 LINQ 提供程序必須以此方式表示源數據。例如,LINQ to XML 將 XML 文檔加載到可查詢的  XElement 類型中:
  //從 XML 中建立數據源
  //using System.Xml.Linq;
  var contacts = XElement.Load(@"c:\xxx.xml");

  

  在 LINQ to SQL 中,首先須要建立對象關係映射。 針對這些對象編寫查詢,而後由 LINQ to SQL 在運行時處理與數據庫的通訊。

1     var  db = new Northwnd(@"c:\northwnd.mdf");
2     
3     //查詢在倫敦的客戶
4     var custQuery =
5         from cust in db.Customers
6         where cust.City == "London"
7         select cust;
Customers 表示數據庫中的特定表

 

  1.3 查詢

  查詢指定要從數據源中檢索的信息。 查詢還能夠指定在返回這些信息以前如何對其進行排序、分組和結構化。 查詢存儲在查詢變量中,並用查詢表達式進行初始化。

  以前的示例中的查詢是從整數數組中返回全部的偶數。 該查詢表達式包含三個子句: fromwhere 和  select。(若是您熟悉 SQL,您會注意到這些子句的順序與 SQL 中的順序相反。) from 子句指定數據源, where 子句指定應用篩選器, select 子句指定返回的元素的類型。 目前須要注意的是,在 LINQ 中, 查詢變量自己不執行任何操做而且不返回任何數據。 它只是存儲在之後某個時刻執行查詢時爲生成結果而必需的信息。
 

  1.4 查詢執行

  1.延遲執行

    如前所述,查詢變量自己只是存儲查詢命令。  實際的查詢執行會延遲到在 foreach 語句中循環訪問查詢變量時發生。 此概念稱爲「延遲執行」。

  2.強制當即執行

    對一系列源元素執行聚合函數的查詢必須首先循環訪問這些元素。CountMaxAverage 和 First 就屬於此類查詢。因爲查詢自己必須使用 foreach 以便返回結果,所以這些查詢在執行時不使用顯式 foreach 語句。另外還要注意,這些類型的查詢返回單個值,而不是 IEnumerable 集合。 

1     var numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
2 
3     var evenNumQuery =
4         from num in numbers
5         where (num % 2) == 0
6         select num;
7 
8     var evenNumCount = evenNumQuery.Count();
View Code

 

  若要強制當即執行任意查詢並緩存其結果,能夠調用 ToList<TSource> 或 ToArray<TSource> 方法。

1     var numQuery2 =
2            (from num in numbers
3             where (num % 2) == 0
4             select num).ToList();
5 
6     var numQuery3 =
7           (from num in numbers
8            where (num % 2) == 0
9             select num).ToArray();
View Code

 

  此外,還能夠經過在緊跟查詢表達式以後的位置放置一個 foreach 循環來強制執行查詢。可是,經過調用 ToList 或 ToArray,也能夠將全部數據緩存在單個集合對象中。 

 

2、基本 LINQ 查詢操做

  2.1 獲取數據源:from

  在 LINQ 查詢中,第一步是指定數據源。像在大多數編程語言中同樣,必須先聲明變量,才能使用它。在 LINQ 查詢中,最早使用 from 子句的目的是引入數據源和範圍變量。

1     //queryAllCustomers 是 IEnumerable<Cutsomer> 類型
2     //數據源 (customers) 和範圍變量 (cust)
3     var queryAllCustomers = from cust in customers
4                                            select cust;
View Code

  範圍變量相似於 foreach 循環中的迭代變量,但在查詢表達式中,實際上不發生迭代。執行查詢時,範圍變量將用做對 customers 中的每一個後續元素的引用。由於編譯器能夠推斷 cust 的類型,因此您沒必要顯式指定此類型。

 

  2.2 篩選:where

  也許最經常使用的查詢操做是應用布爾表達式形式的篩選器。此篩選器使查詢只返回那些表達式結果爲 true 的元素。使用 where 子句生成結果。實際上,篩選器指定從源序列中排除哪些元素。

1     var queryLondonCustomers = from cust in customers
2                                   where cust.City = "London"
3                                     select cust;
只返回地址位於倫敦的 customers。

  您可使用熟悉的 C# 邏輯 AND(&&)和 OR(||) 運算符來根據須要在 where 子句中應用任意數量的篩選表達式。 

where cust.City = "London" && cust.Name = "Devon"
若要只返回位於「倫敦」和姓名爲「Devon」的客戶
where cust.City = "London" || cust.Name = "Paris"
若要返回位於倫敦或巴黎的客戶

 

  2.3 排序:orderby

  一般能夠很方便地將返回的數據進行排序。orderby 子句將使返回的序列中的元素按照被排序的類型的默認比較器進行排序。

1     var queryLondonCustomers = from cust in customers
2                                where cust.City = "London"
3                                orderby cust.Name descending 
4                                select cust;
按 Name 屬性對結果進行排序

  由於 Name 是一個字符串,因此默認比較器執行從 A 到 Z 的字母排序。若要按相反順序(從 Z 到 A)對結果進行排序,請使用 orderby…descending 子句。

 

  2.4 分組:group

  使用 group 子句,您能夠按指定的鍵分組結果。

 1     var queryLondonCustomers = from cust in customers
 2                     group cust by cust.City;
 3 
 4     foreach (var queryLondonCustomer in queryLondonCustomers)
 5     {
 6        Console.WriteLine(queryLondonCustomer.Key);
 7        foreach (var cust in queryLondonCustomer)
 8        {
 9           Console.WriteLine(cust.Name);
10        }
11     }
您能夠指定結果應按 City 分組,以便位於倫敦或巴黎的全部客戶位於各自組中。

  在本例中,cust.City 是鍵。

  在使用 group 子句結束查詢時,結果採用列表的列表形式。列表中的每一個元素是一個具備 Key 成員及根據該鍵分組的元素列表的對象。在循環訪問生成組序列的查詢時,您必須使用嵌套的 foreach 循環。外部循環用於循環訪問每一個組,內部循環用於循環訪問每一個組的成員。  

  若是您必須引用組操做的結果,可使用 into 關鍵字來建立可進一步查詢的標識符。

1     //custQuery 是 IEnumable<IGrouping<string, Customer>> 類型
2     var custQuery = from cust in customers
3                     group cust by cust.City
4                     into custGroup
5                     where custGroup.Count() > 2
6                     orderby custGroup.Key
7                     select custGroup;
這裏的查詢只返回那些包含兩個以上的客戶的組。

 

  2.5 聯接:join

  聯接運算建立數據源中沒有顯式建模的序列之間的關聯。例如,您能夠執行聯接來查找位於同一地點的全部客戶和經銷商。在 LINQ 中,join 子句始終針對對象集合而非直接針對數據庫表運行。  

1     var innerJoinQuery = from cust in customers
2                        join dist in distributors on cust.City equals dist.City
3                        select new {CustomerName = cust.Name, DistributorName = dist.Name};
例如,您能夠執行聯接來查找位於同一地點的全部客戶和經銷商。

  在 LINQ 中,join 子句始終針對對象集合而非直接針對數據庫表運行。  

  在 LINQ 中,您沒必要像在 SQL 中那樣頻繁使用 join,由於 LINQ 中的外鍵在對象模型中表示爲包含項集合的屬性。

    from order in Customer.Orders...
例如,Customer 對象包含 Order 對象的集合。沒必要執行聯接,只需使用點表示法訪問訂單。

  

  2.6 選擇(投影):select

  select 子句生成查詢結果並指定每一個返回的元素的「形狀」或類型。

  例如,您能夠指定結果包含的是整個 Customer 對象、僅一個成員、成員的子集,仍是某個基於計算或新對象建立的徹底不一樣的結果類型。當 select 子句生成除源元素副本之外的內容時,該操做稱爲「投影」

 

3、使用 LINQ 進行數據轉換

  語言集成查詢 (LINQ) 不只可用於檢索數據,並且仍是一個功能強大的數據轉換工具。經過使用 LINQ 查詢,您能夠將源序列用做輸入,並採用多種方式修改它以建立新的輸出序列。您能夠經過排序和分組來修改該序列,而沒必要修改元素自己。可是,LINQ 查詢的最強大的功能是可以建立新類型。這一功能在 select 子句中實現。 例如,能夠執行下列任務:  

  

  3.1 將多個輸入聯接到一個輸出序列

 1     class Student
 2     {
 3         public string Name { get; set; }
 4 
 5         public int Age { get; set; }
 6 
 7         public string City { get; set; }
 8 
 9         public List<int> Scores { get; set; }
10     }
11 
12     class Teacher
13     {
14         public int Id { get; set; }
15 
16         public string Name { get; set; }
17 
18         public int Age { get; set; }
19 
20         public string City { get; set; }
21 
22     }
學生和老師兩個類
 1     internal class Program
 2     {
 3         private static void Main(string[] args)
 4         {
 5             //建立第一個數據源
 6             var students = new List<Student>()
 7             {
 8                 new Student()
 9                 {
10                     Age = 23,
11                     City = "廣州",
12                     Name = "小C",
13                     Scores = new List<int>(){85,88,83,97}
14                 },
15                 new Student()
16                 {
17                     Age = 18,
18                     City = "廣西",
19                     Name = "小明",
20                     Scores = new List<int>(){86,78,85,90}
21                 },
22                 new Student()
23                 {
24                     Age = 33,
25                     City = "夢裏",
26                     Name = "小叄",
27                     Scores = new List<int>(){86,68,73,97}
28                 }
29             };
30 
31             //建立第二個數據源
32             var teachers = new List<Teacher>()
33             {
34                 new Teacher()
35                 {
36                     Age = 35,
37                     City = "夢裏",
38                     Name = "啵哆"
39                 },
40                 new Teacher()
41                 {
42                     Age = 28,
43                     City = "雲南",
44                     Name = "小紅"
45                 },
46                 new Teacher()
47                 {
48                     Age = 38,
49                     City = "河南",
50                     Name = "麗麗"
51                 }
52             };
53 
54             //建立查詢
55             var peopleInDreams = (from student in students
56                             where student.City == "夢裏"
57                             select student.Name)
58                             .Concat(from teacher in teachers
59                                     where teacher.City == "夢裏"
60                                     select teacher.Name);
61 
62             //執行查詢
63             foreach (var person in peopleInDreams)
64             {
65                 Console.WriteLine(person);
66             }
67 
68             Console.Read();
69         }
70     }
控制檯輸出代碼。   

 

  3.2 選擇各個源元素的子集

  1. 若要只選擇源元素的一個成員,請使用點運算。

1     var query = from cust in Customers
2                     select cust.City;

  

  2. 若要建立包含源元素的多個屬性的元素,可使用具備命名對象或匿名類型的對象初始值設定項。

1     var query = from cust in Customer
2                    select new {Name = cust.Name, City = cust.City};

 

  3.3 將內存中的對象轉換爲 XML

 1             //建立數據源
 2             var students = new List<Student>()
 3             {
 4                 new Student()
 5                 {
 6                     Age = 18,
 7                     Name = "小A",
 8                     Scores = new List<int>() {88,85,74,66 }
 9                 },
10                 new Student()
11                 {
12                     Age = 35,
13                     Name = "小B",
14                     Scores = new List<int>() {88,85,74,66 }
15                 },
16                 new Student()
17                 {
18                     Age = 28,
19                     Name = "小啥",
20                     Scores = new List<int>() {88,85,74,66 }
21                 }
22             };
23 
24             //建立查詢
25             var studentsToXml = new XElement("Root",
26                 from student in students
27                 let x = $"{student.Scores[0]},{student.Scores[1]},{student.Scores[2]},{student.Scores[3]}"
28                 select new XElement("student",
29                 new XElement("Name", student.Name),
30                 new XElement("Age", student.Age),
31                 new XElement("Scores", x))
32             );
33 
34             //執行查詢
35             Console.WriteLine(studentsToXml);
View Code

 

  3.4 對源元素執行操做

  輸出序列可能不包含源序列的任何元素或元素屬性。輸出多是經過將源元素用做輸入參數計算出的值的序列。

 1             //數據源
 2             double[] radii = {1, 2, 3};
 3 
 4             //建立查詢
 5             var query = from radius in radii
 6                 select $"{radius * radius * 3.14}";
 7 
 8             //執行查詢
 9             foreach (var i in query)
10             {
11                 Console.WriteLine(i);
12             }
View Code

   【備註】$"{radius * radius * 3.14}" 至關於 string.Format("{0}",radius * radius * 3.14),這裏採用的是 C# 6.0 的語法。

 

4、LINQ 查詢操做的類型關係

  LINQ 查詢操做在數據源、查詢自己及查詢執行中是強類型的。查詢中變量的類型必須與數據源中元素的類型和 foreach 語句中迭代變量的類型兼容。強類型能夠保證在編譯時捕獲類型錯誤,以便及時改正。

 

  4.1 不轉換源數據的查詢

  下圖演示不對數據執行轉換的 LINQ to Objects 查詢操做。源包含一個字符串序列,查詢輸出也是一個字符串序列。 

  ①數據源的類型參數決定範圍變量的類型。

  ②選擇的對象的類型決定查詢變量的類型。此處的 name 爲一個字符串。所以,查詢變量是一個 IEnumerable<字符串>。  

  ③在 foreach 語句中循環訪問查詢變量。由於查詢變量是一個字符串序列,因此迭代變量也是一個字符串。  

 

  4.2 轉換源數據的查詢

  下圖演示對數據執行簡單轉換的 LINQ to SQL 查詢操做。查詢將一個 Customer 對象序列用做輸入,並只選擇結果中的 Name 屬性。由於 Name 是一個字符串,因此查詢生成一個字符串序列做爲輸出。  

  ①數據源的類型參數決定範圍變量的類型。

  ②select 語句返回 Name 屬性,而非完整的 Customer 對象。由於 Name 是一個字符串,因此 custNameQuery 的類型參數是 string,而非Customer。  

  ③由於 custNameQuery 是一個字符串序列,因此 foreach 循環的迭代變量也必須是 string

 

  下圖演示另外一種轉換。select 語句返回只捕獲原始 Customer 對象的兩個成員的匿名類型。

  ①數據源的類型參數始終爲查詢中的範圍變量的類型。

  ②由於 select 語句生成匿名類型,因此必須使用 var 隱式類型化查詢變量。

  ③由於查詢變量的類型是隱式的,因此 foreach 循環中的迭代變量也必須是隱式的。

 

  4.3 讓編譯器推斷類型信息

  您也可使用關鍵字 var,可用於查詢操做中的任何局部變量。可是,編譯器爲查詢操做中的各個變量提供強類型  

 

5、LINQ 中的查詢語法和方法語法

  咱們編寫的 LINQ 查詢語法,在編譯代碼時,CLR 會將查詢語法轉換爲方法語法。這些方法調用標準查詢運算符的名稱相似 WhereSelectGroupByJoinMax和 Average,咱們也是能夠直接使用這些方法語法的。  

  查詢語法和方法語法語義相同,可是,許多人員發現查詢語法更簡單、更易於閱讀。某些查詢必須表示爲方法調用。例如,必須使用方法調用表示檢索元素的數量與指定的條件的查詢。還必須使用方法須要檢索元素的最大值在源序列的查詢。System.Linq 命名空間中的標準查詢運算符的參考文檔一般使用方法語法。

 

  5.1 標準查詢運算符擴展方法

 1         static void Main(string[] args)
 2         {
 3             var nums = new int[4] { 1, 2, 3, 4 };
 4             
 5             //建立查詢表達式
 6             var qureyNums = from n in nums
 7                             where n % 2 == 0
 8                             orderby n descending
 9                             select n;
10 
11             Console.WriteLine("qureyNums:");
12             foreach (var n in qureyNums)
13             {
14                 Console.WriteLine(n);
15             }
16     
17             //使用方法進行查詢
18             var queryNums2 = nums.Where(n => n % 2 == 0).OrderByDescending(n => n);
19 
20             Console.WriteLine("qureyNums2:");
21             foreach (var n in queryNums2)
22             {
23                 Console.WriteLine(n);
24             }
25 
26             Console.Read();
27         }
下面的示例演示簡單的查詢表達式和編寫爲基於方法的查詢的語義上等效的查詢。

  兩個示例的輸出是相同的。您能夠看到兩種形式的查詢變量的類型是相同的:IEnumerable<T>。  

  若要了解基於方法的查詢,讓咱們進一步地分析它。注意,在表達式的右側,where 子句如今表示爲對 numbers 對象的實例方法,在您從新調用該對象時其類型爲 IEnumerable<int>。若是您熟悉泛型 IEnumerable<T> 接口,那麼您就會了解,它不具備 Where 方法。可是,若是您在 Visual Studio IDE 中調用 IntelliSense 完成列表,那麼您不只將看到 Where 方法,並且還會看到許多其餘方法,如 SelectSelectManyJoin 和Orderby。下面是全部標準查詢運算符。 

  儘管看起來 IEnumerable<T> 彷佛已被從新定義以包括這些附加方法,但事實上並不是如此。這些標準查詢運算符都是做爲「擴展方法」實現的。

 

  5.2 Lambda 表達式

  在前面的示例中,通知該條件表達式 (num % 2 == 0) 是做爲內聯參數Where 方法:Where(num => num % 2 == 0) 此內聯表達式稱爲 lambda 表達式。將代碼編寫爲匿名方法或泛型委託或表達式樹是一種便捷的方法,不然編寫起來就要麻煩得多。=> 是 lambda 運算符,可讀爲「goes to」。運算符左側的 num 是輸入變量,與查詢表達式中的 num 相對應。編譯器可推斷 num 的類型,由於它瞭解 numbers 是泛型 IEnumerable<T> 類型。lambda 表達式與查詢語法中的表達式或任何其餘 C# 表達式或語句中的表達式相同;它能夠包括方法調用和其餘複雜邏輯。「返回值」就是表達式結果。  

 

  5.3 查詢的組合性

  在上面的代碼示例中,請注意 OrderBy 方法是經過在對 Where 的調用中使用點運算符來調用的。Where 生成篩選序列,而後 Orderby 經過對該序列排序來對它進行操做。由於查詢會返回 IEnumerable,因此您可經過將方法調用連接在一塊兒,在方法語法中將這些查詢組合起來。這就是在您經過使用查詢語法編寫查詢時編譯器在後臺所執行的操做。而且因爲查詢變量不存儲查詢的結果,所以您能夠隨時修改它或將它用做新查詢的基礎,即便在執行它後。

 

傳送門

  入門:《走進 LINQ 的世界

  進階:《LINQ 標準查詢操做概述(強烈推薦)

  技巧:《Linq To Objects - 如何操做字符串》 和 《Linq To Objects - 如何操做文件目錄

 

 


本文首聯:http://www.cnblogs.com/liqingwen/p/5832322.html

【參考】https://msdn.microsoft.com/zh-cn/library/bb397897(v=vs.100).aspx 等

【來源】本文引用部分微軟官方文檔的圖片

相關文章
相關標籤/搜索