本筆記摘抄自:https://www.cnblogs.com/liqingwen/p/5832322.html,記錄一下學習過程以備后續查用。
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 查詢操作的三個部分
操作三部曲:①取數據源 ②創建查詢 ③執行查詢
下面代碼演示LINQ to OBJECT:

class Program { static void Main(string[] args) { #region LINQ to OBJECT //查詢三部曲:1、獲取數據源 var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; //查詢三部曲:2、創建查詢 var query = from num in nums where (num % 2) == 0 select num; //查詢三部曲:3、執行查詢 foreach (var num in query) { Console.Write($"{num} "); } Console.Read(); #endregion } }
運行結果如下:
下圖顯示了完整的查詢操作。在LINQ中,查詢的執行與查詢本身截然不同。換句話說,查詢本身指的是只創建查詢變量,不檢索任何數據。
1.2 數據源
在上一個示例中(LINQ to OBJECT),由於數據源是數組,因此它隱式支持泛型 IEnumerable<T> (可枚舉)接口。支持 IEnumerable<T> 或派生接口(如泛型IQueryable<T>)的類型稱為可查詢類型。
可查詢類型不需要進行修改或特殊處理就可以用作LINQ數據源。如果源數據還沒有作為可查詢類型出現在內存中,則LINQ提供程序必須以此方式表示源數據。例如,LINQ to XML將XML文檔加載到可查詢的 XElement 類型中。
下面代碼演示LINQ to XML:
創建一個Test.xml文件,放在主目錄下。

<DocumentElement> <Category> <MO_NO>MOA1911070001</MO_NO> <MRP_NO>8198712090963008</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110701</BIL_NO> </Category> <Category> <MO_NO>MOA1911070002</MO_NO> <MRP_NO>8193000000003172</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110702</BIL_NO> </Category> <Category> <MO_NO>MOA1911070003</MO_NO> <MRP_NO>8193002043133003</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110702</BIL_NO> </Category> <Category> <MO_NO>MOA1911070004</MO_NO> <MRP_NO>8193002043133004</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110702</BIL_NO> </Category> <Category> <MO_NO>MOA1911070005</MO_NO> <MRP_NO>8193002043133005</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110702</BIL_NO> </Category> <Category> <MO_NO>MOA1911070006</MO_NO> <MRP_NO>8198922092971001</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110703</BIL_NO> </Category> <Category> <MO_NO>MOA1911070007</MO_NO> <MRP_NO>8198922092971002</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110703</BIL_NO> </Category> <Category> <MO_NO>MOA1911070008</MO_NO> <MRP_NO>8198922092971010</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110703</BIL_NO> </Category> <Category> <MO_NO>MOA1911070009</MO_NO> <MRP_NO>8198922092971200</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110703</BIL_NO> </Category> <Category> <MO_NO>MOA1911070010</MO_NO> <MRP_NO>8199862094443008</MRP_NO> <QTY>1.00000000</QTY> <BIL_NO>MPA19110704</BIL_NO> </Category> </DocumentElement>

class Program { static void Main(string[] args) { #region LINQ to XML var xe = XElement.Load(@"..\..\Test.xml"); var query = from item in xe.Descendants("Category") select item; foreach (var item in query) { Console.WriteLine($"MO_NO={item.Element("MO_NO").Value}"); } Console.Read(); #endregion } }
運行結果如下:
在LINQ to SQL中,首先需要生成對象模型映射到關系數據庫,然后針對這些對象編寫查詢,由LINQ to SQL在運行時處理與數據庫的通信。
准備一:下文中用到的Northwind數據庫,下載地址為:https://www.microsoft.com/en-us/download/confirmation.aspx?id=23654
准備二:若本機沒有安裝LINQ to SQL工具,可參考安裝教程:https://blog.csdn.net/u011176794/article/details/90287293
准備三:添加新建項,選擇LINQ to SQL類,命名為Sample。
准備四:服務器資源管理器的數據連接中,右鍵添加連接,依導向連接至SQL Server數據庫。
准備五:將相關數據表拖至Sample.dbml中並保存。
准備六:添加引用System.Data.Linq。
下面代碼演示LINQ to SQL:

class Program { static void Main(string[] args) { #region LINQ to SQL var db = new SampleDataContext(); var query = from cust in db.Customers where cust.City == "London" select cust; foreach (var item in query) { Console.WriteLine($"CustomerID={item.CustomerID}"); } Console.Read(); #endregion } }
運行結果如下:
查詢指定要從數據源中檢索的信息,可以指定在返回這些信息之前如何對其進行排序、分組和結構化。 查詢存儲在查詢變量中,並用查詢表達式進行初始化。之前的示例中的查詢是從整數數組中返回所有的偶數。該查詢表達式包含三個子句:from、where和select。如果您熟悉SQL,您會注意到這些子句的順序與SQL中的順序相反,from 子句指定數據源,where 子句指定應用篩選器,select 子句指定返回的元素的類型。目前需要注意的是,在LINQ中,查詢變量本身不執行任何操作並且不返回任何數據,它只是存儲在以后某個時刻執行查詢時為生成結果而必需的信息。
1.4 查詢執行
1.延遲執行
如前所述,查詢變量本身只是存儲查詢命令,實際的查詢執行會延遲到在foreach語句中循環訪問查詢變量時發生,此概念稱為“延遲執行”。
2.強制立即執行
對一系列源元素執行聚合函數的查詢必須首先循環訪問這些元素,Count、Max、Average和First就屬於此類查詢。由於查詢本身必須使用foreach以便返回結果,因此這些查詢在執行時不使用顯式foreach語句。另外還要注意,這些類型的查詢返回單個值,而不是IEnumerable集合。

class Program { static void Main(string[] args) { #region LINQ查詢強制立即執行一 var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; var query = from num in nums where (num % 2) == 0 select num; var numCount = query.Count(); Console.WriteLine($"NumCount={numCount}"); Console.Read(); #endregion } }

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

class Program { static void Main(string[] args) { #region LINQ查詢強制立即執行二 var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; var query2 = (from num in nums where (num % 2) == 0 select num).ToList(); var query3 = (from num in nums where (num % 2) == 0 select num).ToArray(); Console.WriteLine($"NumCount={query2.Count}"); Console.WriteLine($"NumCount={query3.Length}"); Console.Read(); #endregion } }
運行結果如下:
二、基本 LINQ 查詢操作
2.1 獲取數據源:from
在LINQ查詢中,第一步是指定數據源。像在大多數編程語言中一樣,必須先聲明變量,才能使用它。在LINQ查詢中,最先使用from子句的目的是引入數據源和范圍變量。

class Program { static void Main(string[] args) { #region LINQ to SQL var db = new SampleDataContext(); //query是IEnumerable<Cutsomer>類型 //數據源(db.customers)和范圍變量(cust) var query = from cust in db.Customers where cust.City == "London" select cust; foreach (var item in query) { Console.WriteLine($"CustomerID={item.CustomerID}"); } Console.Read(); #endregion } }
范圍變量類似於foreach循環中的迭代變量,但在查詢表達式中,實際上不發生迭代。執行查詢時,范圍變量將用作對customers中的每個后續元素的引用。因為編譯器可以推斷cust的類型,所以您不必顯式指定此類型。
2.2 篩選:where
也許最常用的查詢操作是應用布爾表達式形式的篩選器,此篩選器使查詢只返回那些表達式結果為true的元素。使用where子句生成結果,實際上,篩選器指定從源序列中排除哪些元素。
您可以使用熟悉的C#邏輯AND(&&)和OR(||)運算符來根據需要在where子句中應用任意數量的篩選表達式。

class Program { static void Main(string[] args) { #region LINQ 篩選:where var db = new SampleDataContext(); var query1 = from cust in db.Customers where cust.City == "London" && cust.CustomerID == "AROUT" select cust; var query2 = from cust in db.Customers where cust.City == "London" || cust.City == "Paris" select cust; foreach (var item in query1) { Console.WriteLine($"query1->City={item.City},CustomerID={item.CustomerID}"); } foreach (var item in query2) { Console.WriteLine($"query2->City={item.City},CustomerID={item.CustomerID}"); } Console.Read(); #endregion } }
運行結果如下:
2.3 排序:orderby
通常可以很方便地將返回的數據進行排序。orderby子句將使返回的序列中的元素按照被排序的類型的默認比較器進行排序。

class Program { static void Main(string[] args) { #region LINQ 排序:orderby var db = new SampleDataContext(); var query = from cust in db.Customers where cust.City == "London" orderby cust.CustomerID select cust; foreach (var item in query) { Console.WriteLine($"CustomerID={item.CustomerID}"); } Console.Read(); #endregion } }
運行結果如下:
2.4 分組:group
使用group子句,您可以按指定的鍵分組結果。

class Program { static void Main(string[] args) { #region LINQ 分組一:group var db = new SampleDataContext(); var query = from cust in db.Customers where cust.City == "London" || cust.City == "Paris" group cust by cust.City; foreach (var group in query) { Console.WriteLine(group.Key); foreach (var cust in group) { Console.WriteLine($"City={cust.City},CustomerID={cust.CustomerID}"); } } Console.Read(); #endregion } }
運行結果如下:
在本例中,cust.City是鍵。
在使用group子句結束查詢時,結果采用列表的列表形式。列表中的每個元素是一個具有Key成員及根據該鍵分組的元素列表的對象。在循環訪問生成組序列的查詢時,您必須使用嵌套的foreach循環。外部循環用於循環訪問每個組,內部循環用於循環訪問每個組的成員。
如果您必須引用組操作的結果,可以使用into關鍵字來創建可進一步查詢的標識符。

class Program { static void Main(string[] args) { #region LINQ 分組二:group var db = new SampleDataContext(); var query = from cust in db.Customers where cust.City == "London" || cust.City == "Paris" group cust by cust.City into custGroup where custGroup.Count() > 2 orderby custGroup.Key select custGroup; foreach (var group in query) { Console.WriteLine(group.Key); foreach (var cust in group) { Console.WriteLine($"City={cust.City},CustomerID={cust.CustomerID}"); } } Console.Read(); #endregion } }
運行結果如下:
2.5 聯接:join
聯接運算創建數據源中沒有顯式建模的序列之間的關聯。例如,您可以執行聯接來查找位於同一地點的所有客戶和經銷商。在LINQ中,join子句始終針對對象集合而非直接針對數據庫表運行。

class Program { static void Main(string[] args) { #region LINQ 聯接:join var db = new SampleDataContext(); var qurey = from order in db.Orders join cust in db.Customers on order.CustomerID equals cust.CustomerID select new { order.OrderID, order.CustomerID, cust.ContactName }; foreach (var item in qurey.Take(5)) { Console.WriteLine($"OrderID={item.OrderID},CustomerID={item.CustomerID},ContactName={item.ContactName}"); } Console.Read(); #endregion } }
運行結果如下:
2.6 選擇(投影):select
select子句生成查詢結果並指定每個返回的元素的“形狀”或類型。
例如,您可以指定結果包含的是整個Customer對象、僅一個成員、成員的子集或者某個基於計算或新對象創建的完全不同的結果類型。當select子句生成除源元素副本以外的內容時,該操作稱為“投影”。
三、使用 LINQ 進行數據轉換
語言集成查詢 (LINQ) 不但是檢索數據的利器,而且還是一個功能強大的數據轉換工具。通過使用LINQ查詢,您可以將源序列用作輸入,並采用多種方式修改它以創建新的輸出序列。您可以通過排序及分組來修改該序列,而不必修改元素本身。但是,LINQ 查詢的最強大的功能是能夠創建新類型。這一功能在select子句中實現。
3.1 將多個輸入聯接到一個輸出序列

/// <summary> /// 學生類 /// </summary> class Student { public string Name { get; set; } public int Age { get; set; } public string City { get; set; } public List<int> Scores { get; set; } } /// <summary> /// 教師類 /// </summary> class Teacher { public string Name { get; set; } public int Age { get; set; } public string City { get; set; } } class Program { static void Main(string[] args) { #region LINQ 將多個輸入聯接到一個輸出序列 //創建第一個數據源 var students = new List<Student>() { new Student() { Age = 19, City = "廣州", Name = "小A", Scores = new List<int>(){85,88,83,97} }, new Student() { Age = 19, City = "深圳", Name = "小B", Scores = new List<int>(){86,80,85,92} } }; //創建第二個數據源 var teachers = new List<Teacher>() { new Teacher() { Age = 30, City = "廣州", Name = "張A" }, new Teacher() { Age = 31, City = "廣州", Name = "李A" } }; //創建查詢 var query = ( from student in students where student.City == "廣州" select student.Name ).Concat ( from teacher in teachers where teacher.City == "廣州" select teacher.Name ); //執行查詢 foreach (var person in query) { Console.WriteLine(person); } Console.Read(); #endregion } }
運行結果如下:
3.2 選擇各個源元素的子集
1. 若要只選擇源元素的一個成員,請使用點運算。
var query = from cust in db.Customers select cust.City;
2. 若要創建包含源元素的多個屬性的元素,可以使用具有命名對象或匿名類型的對象初始值設定項。
var query = from cust in db.Customer select new { cust.Name, cust.City };
3.3 將內存中的對象轉換為XML

/// <summary> /// 學生類 /// </summary> class Student { public string Name { get; set; } public int Age { get; set; } public string City { get; set; } public List<int> Scores { get; set; } } class Program { static void Main(string[] args) { #region LINQ 將內存中的對象轉換為XML //創建數據源 var students = new List<Student>() { new Student() { Age = 19, City = "廣州", Name = "小A", Scores = new List<int>(){85,88,83,97} }, new Student() { Age = 19, City = "深圳", Name = "小B", Scores = new List<int>(){86,80,85,92} } }; //創建查詢 var studentsToXml = new XElement ( "Root", from student in students let x = $"{student.Scores[0]},{student.Scores[1]},{student.Scores[2]},{student.Scores[3]}" select new XElement ( "student", new XElement("Name", student.Name), new XElement("Age", student.Age), new XElement("Scores", x) ) ); //執行查詢 Console.WriteLine(studentsToXml); Console.Read(); #endregion } }
運行結果如下:
3.4 對源元素執行操作
輸出序列可能不包含源序列的任何元素或元素屬性,它可能是通過將源元素用作輸入參數計算出的值的序列。

class Program { static void Main(string[] args) { #region LINQ 對源元素執行操作 //數據源 double[] radius = { 1, 2, 3 }; //創建查詢 var query = from radiu in radius select $"{3.14 * radiu * radiu}"; //執行查詢 foreach (var item in query) { Console.WriteLine(item); } Console.Read(); #endregion } }
運行結果如下:
四、LINQ 查詢操作的類型關系
LINQ 查詢操作在數據源、查詢本身及查詢執行中是強類型的。查詢中變量的類型必須與數據源中元素的類型和foreach語句中迭代變量的類型兼容。強類型可以保證在編譯時捕獲類型錯誤,以便及時改正。
4.1 不轉換源數據的查詢
下圖演示不對數據執行轉換的LINQ to OBJECT查詢操作。源包含一個字符串序列,查詢輸出也是一個字符串序列。
①數據源的類型參數決定范圍變量的類型。
②select語句返回Name屬性,而非完整的Customer對象。因為Name是一個字符串,所以custNameQuery的類型參數是string,而非Customer。
③因為custNameQuery是一個字符串序列,所以foreach循環的迭代變量也必須是string。
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,可用於查詢操作中的任何局部變量。但是,編譯器為查詢操作中的各個變量提供強類型。
五、LINQ 中的查詢語法和方法語法
我們編寫的LINQ查詢語法,在編譯代碼時,CLR會將查詢語法轉換為方法語法。這些方法調用標准查詢運算符的名稱類似Where、Select、GroupBy、Join、Max和Average,我們也是可以直接使用這些方法語法的。
查詢語法和方法語法語義相同,但是,許多人員發現查詢語法更簡單、更易於閱讀。某些查詢必須表示為方法調用。例如,必須使用方法調用表示檢索元素的數量與指定的條件的查詢,還必須使用方法需要檢索元素的最大值在源序列的查詢。System.Linq命名空間中的標准查詢運算符的參考文檔通常使用方法語法。
5.1 標准查詢運算符擴展方法

class Program { static void Main(string[] args) { #region LINQ 標准查詢運算符擴展方法 var nums = new int[4] { 1, 2, 3, 4 }; //創建查詢表達式 var query1 = from num in nums where num % 2 == 0 orderby num descending select num; Console.WriteLine("Query1's result:"); foreach (var num in query1) { Console.WriteLine(num); } //使用方法進行查詢 var query2 = nums.Where(num => num % 2 == 0).OrderByDescending(num => num); Console.WriteLine("Query2's result:"); foreach (var num in query2) { Console.WriteLine(num); } Console.Read(); #endregion } }
運行結果如下:
兩個示例的輸出是相同的。您可以看到兩種形式的查詢變量的類型是相同的:IEnumerable<T>。
若要了解基於方法的查詢,讓我們進一步地分析它。注意,在表達式的右側,where子句現在表示為對numbers對象的實例方法,在您重新調用該對象時其類型為IEnumerable<int>。如果您熟悉泛型 IEnumerable<T> 接口,那么您就會了解,它不具有Where方法。但是,如果您在Visual Studio IDE中調用IntelliSense完成列表,那么您不僅將看到Where方法,而且還會看到許多其他方法,如Select、SelectMany、Join 和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,所以您可通過將方法調用鏈接在一起,在方法語法中將這些查詢組合起來。這就是在您通過使用查詢語法編寫查詢時編譯器在后台所執行的操作,並且由於查詢變量不存儲查詢的結果,因此您可以隨時修改它或將它用作新查詢的基礎,即使在執行它后。