開始接觸 LINQ 序 在此之前曾發表過三篇關於 LINQ 的隨筆: 進階:《LINQ 標準查詢操作概述》(強烈推薦) 技巧:《Linq To Objects - 如何操作字元串》 和 《Linq To Objects - 如何操作文件目錄》 現在,自己打算再整理一篇關於 LINQ 入門的隨筆,也是 ...
開始接觸 LINQ
序
在此之前曾發表過三篇關於 LINQ 的隨筆:
進階:《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 。
一、介紹 LINQ 查詢
查詢是一種從數據源檢索數據的表達式。隨著時間的推移,人們已經為各種數據源開發了不同的語言;例如,用於關係資料庫的 SQL 和用於 XML 的 XQuery。因此,開發人員不得不針對他們必須支持的每種數據源或數據格式而學習新的查詢語言。LINQ 通過提供一種跨數據源和數據格式使用數據的一致模型,簡化了這一情況。在 LINQ 查詢中,始終會用到對象。可以使用相同的編碼模式來查詢和轉換 XML 文檔、SQL 資料庫、ADO.NET 數據集、.NET 集合中的數據以及對其有 LINQ 提供程式可用的任何其他格式的數據。
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>)的類型稱為可查詢類型。
可查詢類型不需要進行修改或特殊處理就可以用作 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 查詢
查詢指定要從數據源中檢索的信息。 查詢還可以指定在返回這些信息之前如何對其進行排序、分組和結構化。 查詢存儲在查詢變數中,並用查詢表達式進行初始化。
之前的示例中的查詢是從整數數組中返回所有的偶數。 該查詢表達式包含三個子句:from、where 和 select。(如果您熟悉 SQL,您會註意到這些子句的順序與 SQL 中的順序相反。)from 子句指定數據源,where 子句指定應用篩選器,select 子句指定返回的元素的類型。 目前需要註意的是,在 LINQ 中,查詢變數本身不執行任何操作並且不返回任何數據。 它只是存儲在以後某個時刻執行查詢時為生成結果而必需的信息。1.4 查詢執行
1.延遲執行
如前所述,查詢變數本身只是存儲查詢命令。 實際的查詢執行會延遲到在 foreach 語句中迴圈訪問查詢變數時發生。 此概念稱為“延遲執行”。
2.強制立即執行
對一系列源元素執行聚合函數的查詢必須首先迴圈訪問這些元素。Count、Max、Average 和 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,也可以將所有數據緩存在單個集合對象中。
二、基本 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 子句生成除源元素副本以外的內容時,該操作稱為“投影”。
三、使用 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 的語法。
四、LINQ 查詢操作的類型關係
LINQ 查詢操作在數據源、查詢本身及查詢執行中是強類型的。查詢中變數的類型必須與數據源中元素的類型和 foreach 語句中迭代變數的類型相容。強類型可以保證在編譯時捕獲類型錯誤,以便及時改正。
4.1 不轉換源數據的查詢
下圖演示不對數據執行轉換的 LINQ to Objects 查詢操作。源包含一個字元串序列,查詢輸出也是一個字元串序列。
①數據源的類型參數決定範圍變數的類型。
②選擇的對象的類型決定查詢變數的類型。此處的 name 為一個字元串。因此,查詢變數是一個 IEnumerable<字元串>。
③在 foreach 語句中迴圈訪問查詢變數。因為查詢變數是一個字元串序列,所以迭代變數也是一個字元串。
4.2 轉換源數據的查詢
下圖演示對數據執行簡單轉換的 LINQ to SQL 查詢操作。查詢將一