本筆記摘抄自:http://www.javashuo.com/article/p-grqlubti-ck.html,記錄一下學習過程以備後續查用。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。編程語言
1、LINQ查詢ide
查詢是一種從數據源檢索數據的表達式。隨着時間的推移,人們已經爲各類數據源開發了不一樣的語言。例如,用於關係數據庫的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 //查詢三部曲:一、獲取數據源 var nums = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; //查詢三部曲:二、建立查詢 var query = from num in nums where (num % 2) == 0 select num; //查詢三部曲:三、執行查詢 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 } }
運行結果以下:
2、基本 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子句生成除源
元素副本之外的內容時,該操做稱爲「投影」。
3、使用 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 } }
運行結果以下:
4、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,可用於查詢操做中的任何局部變量。可是,編譯器爲查詢操做中的各個變量提供強類型。
5、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,因此您可經過將方法調用連接在一塊兒,在方法語法中將這些查詢組合起來。這就是在您經過使用查詢語法編寫查詢時編譯器在
後臺所執行的操做,而且因爲查詢變量不存儲查詢的結果,所以您能夠隨時修改它或將它用做新查詢的基礎,即便在執行它後。