1、揭開linq的神祕面紗
(一)概述
LINQ的全稱是Language Integrated Query,中文譯成「語言集成查詢」。LINQ做爲一種查詢技術,首先要解決數據源的封裝,大體使用了三大組件來實現這個封裝,分別是LINQ to Object、LINQ to ADO.NET、LINQ to XML。它們和.NET語言的關係以下html
要使用LINQ來編程,首先要學習使用LINQ的子句以及由查詢語法構成的查詢表達式。C#3.0和VB9開始將這種查詢語法引入到了編程語言,並新增了一系列的關鍵字。但對於CLR自己來講,它並不瞭解查詢語法,它能理解的是由編程語言的編譯器將這種查詢語法轉換成的方法。這些方法叫「標準查詢運算符」,它們具備相似這樣的名—Where、Select、GroupBy、Join。下面就以C#爲例,從編程語言的層面來具體介紹這些查詢語法(注意VB9也支持這種查詢語法)。數據庫
LINQ的查詢由3基本部分組成:獲取數據源,建立查詢,執行查詢express
// 1,獲取數據源 List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; // 2,建立查詢 var numQuery = from num in numbers where num % 2 == 0 select num; // 3,執行查詢 foreach (var num in numQuery) { Console.WriteLine("{0,1}", num); }
下圖顯示了完整的查詢操做。在 LINQ 中,查詢的執行與查詢自己大相徑庭;換句話說,若是隻是建立查詢變量,則不會檢索任何數據。編程
如上例所示,Linq的數據源要求必須實現IEnumerable或IEnumerable<T>接口,數組隱式支持這個接口。numQuery叫作查詢變量,它存儲了一個查詢表達式。注意,聲明查詢變量並不會執行查詢,真正的執行查詢延遲到了foreach語句中。數組
linq的機制用到的詳細知識點請參考:https://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html服務器
1. from子句編程語言
建立一個LINQ表達式必需要以from子句開頭。函數
1.1 單個from子句學習
string[] values = { "中國", "日本", "美國", "菲律賓", "越南" }; //查詢包含「國」的字符串 var valueQuery = from v in values where v.IndexOf("國") > 0 select v; foreach (var v in valueQuery) { Console.WriteLine("{0,1}", v); }
在這個LINQ表達式的from子句中,v叫作範圍變量,values是數據源。v的做用域存在於當前的LINQ表達式,表達式之外不能訪問這個變量。where用來篩選元素,select用於輸出元素。這裏的範圍變量v,和foreach語句中得隱式變量v均可以由編譯器推斷出其類型。
運行的結果以下:this
中國
美國
使用LINQ查詢List<T>集合
public class CustomerInfo { public string Name { get; set; } public int Age { get; set; } public string Tel { get; set; } } private void formExpDemo2() { //這裏用了,對象和集合初始化器 List<CustomerInfo> customers = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="諸葛菲菲", Age=23, Tel ="1380524****"} }; //查詢年齡大於20的客戶,注意這裏的範圍變量用了顯示類型CustomerInfo var query = from CustomerInfo ci in customers where ci.Age > 20 select ci; foreach (CustomerInfo ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); } }
結果:
姓名:歐陽曉曉 年齡:35 電話:1330708**** 姓名:諸葛菲菲 年齡:23 電話:1380524****
1.2 複合from子句
在查詢數據源中,元素的屬性是一個集合時,可使用複合from子句對這個屬性集合查詢。好比,一個客戶,可能有多個電話。
public class CustomerInfo { public string Name { get; set; } public int Age { get; set; } public List<string> TelTable { get; set; } } private void formExpDemo() { List<CustomerInfo> customers = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, TelTable=new List<string>{"1330708****","1330709****"}}, new CustomerInfo{ Name="上官飄飄", Age=17, TelTable=new List<string>{"1592842****","1592843****"}}, new CustomerInfo{ Name="諸葛菲菲", Age=23, TelTable=new List<string>{"1380524****","1380525****"}} }; //查詢包含電話號碼1592842****的客戶 var query = from CustomerInfo ci in customers from tel in ci.TelTable where tel.IndexOf("1592842****") > -1 select ci; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1}", ci.Name, ci.Age); foreach (var tel in ci.TelTable) { Console.WriteLine(" 電話:{0}", tel); } } }
結果:
姓名:上官飄飄 年齡:17 電話:1592842**** 電話:1592843****
1.3 多個from子句
多個from子句查詢和複合from子句從字面上看彷佛同樣,實際上是不一樣的操做。複合from子句查詢的是單個數據源中的子元素的集合,而多個from子句,是載入多個數據源進行查詢。
private void formExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="諸葛菲菲", Age=23, Tel ="1380524****"} }; List<CustomerInfo> clist2 = new List<CustomerInfo> { new CustomerInfo{ Name="令狐沖", Age=25, Tel ="1330708****"}, new CustomerInfo{ Name="東方不敗", Age=35, Tel ="1592842****"}, new CustomerInfo{ Name="任盈盈", Age=23, Tel ="1380524****"} }; //在clist中查找Age大於20的客戶, //在clist2中查找Age小於30的客戶 var query = from customer in clist where customer.Age > 20 from customer2 in clist2 where customer2.Age < 30 select new { customer, customer2 }; foreach (var ci in query) { Console.WriteLine("{0} {1}", ci.customer.Name,ci.customer2.Name); } }
在select語句中,咱們用了匿名類型來存儲篩選出的元素,這樣獲得的徹底是一個交叉聯接表,有點相似於SQL中的笛卡爾乘積。
輸出的結果:
歐陽曉曉 令狐沖
歐陽曉曉 任盈盈
諸葛菲菲 令狐沖
諸葛菲菲 任盈盈
二、where子句
where子句的做用就是篩選元素,除了開始和結束位置,where子句幾乎能夠出如今LINQ表達式的任意位置。一個LINQ表達式中能夠有where子句,也能夠沒有;能夠有一個,能夠有多個;多個where子句之間的關係至關於邏輯「與」,每一個where子句能夠包含1個或多個邏輯表達式,這些條件成爲「謂詞」,多個謂詞之間用布爾運算符隔開,好比邏輯「與」用&&,邏輯「或」用||,而不是用SQL中的AND或OR。
2.1 常見的where子句查詢
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐沖", Age=23, Tel ="1380524****"} }; //查詢名字是3個字或者姓「令」的,但年齡大於20的客戶 var query = from customer in clist where (customer.Name.Length == 3 || customer.Name.Substring(0, 1) == "令") && customer.Age > 20 select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); }
結果:
姓名:令狐沖 年齡:23 電話:1380524****
2.2 在where子句中使用自定義函數
private void whereExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐沖", Age=23, Tel ="1380524****"} }; //查詢名字是3個字而且姓「令」的客戶 var query = from customer in clist where (customer.Name.Length == 3 && CheckName(customer.Name)) select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); } } private bool CheckName(string name) { if (name.Substring(0, 1) == "令") return true; else return false; }
結果:
姓名:令狐沖 年齡:23 電話:1380524****
2.3 動態謂詞的篩選
上面的幾個例子都是給定了查詢謂詞而後進行查詢,有時候謂詞的數量可能並不固定,是隨狀況變化的。例如:一組名字多是運行時動態指定的。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐沖", Age=23, Tel ="1380524****"} }; //定義動態的謂詞數組,這個數組應該由實際運行環境生成 string[] names = { "令狐沖", "任盈盈", "楊過", "小龍女", "歐陽曉曉" }; //查詢在給定謂詞數組裏存在的客戶 var query = from customer in clist where names.Contains(customer.Name) select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); }
結果:
姓名:歐陽曉曉 年齡:35 電話:1330708**** 姓名:令狐沖 年齡:23 電話:1380524****
3. select子句
3.1 輸出查詢結果
最簡單的select就是直接輸出from子句創建的那個範圍變量:
var query = from customer in clist where names.Contains(customer.Name) select customer;
也能夠輸出範圍變量類型中得某個屬性:
select customer.Name;
或者修改一下再輸出:
select customer.Name.Replace("gg","mm");
或者乾脆使用一個自定義的函數,把範圍變量傳進去,輸出處理後的結果:
select MyFunction(customer.Name);
3.2 對查詢結果進行投影
public class MyCustomerInfo { public string Name { get; set; } public string Tel { get; set; } } private void whereExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐沖", Age=23, Tel ="1380524****"} }; //定義動態的謂詞數組,這個數組應該由實際運行環境生成 string[] names = { "令狐沖", "任盈盈", "楊過", "小龍女", "歐陽曉曉" }; //查詢在給定謂詞數組裏存在的客戶 var query = from customer in clist where customer.Age < 30 select new MyCustomerInfo { Name = customer.Name, Tel = customer.Tel }; foreach (var ci in query) { Console.WriteLine("姓名:{0} 電話:{1} 類型{2}", ci.Name, ci.Tel,ci.GetType().FullName); } }
上例中,在select子句中用對象初始化器生成了新的數據類型,從而進行了數據轉換,使元素變成了MyCustomerInfo類型。
姓名:上官飄飄 電話:1592842**** 類型LinqDemo.Form1+MyCustomerInfo 姓名:令狐沖 電話:1380524**** 類型LinqDemo.Form1+MyCustomerInfo
4. group子句
按照語法的規定,LINQ表達式必須以from子句開頭,以select或group子句結束,因此除了使用select子句外,也可使用guoup子句來返回元素分組後的結果。group子句返回的是一個IGrouping<TKey,TElement>泛型接口的對象集合,下面先了解下這個接口。
4.1 IGrouping<TKey,TElement>泛型接口
這個接口表示具備公共鍵的對象集合,它的原型以下:
public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable
TKey是鍵的對象類型,在用於group子句的時候,數據類型會有編譯器推斷出來,它通常用於存儲分組的鍵值;TElement是指的對象類型,用於存儲分組的結果,變量基於這個接口的類型就是遍歷這個值。
4.2 分組查詢
分組查詢對於關係型數據庫是很是常見的一種操做,但在沒有LINQ以前,對內存的對象進行分組倒是一件很是麻煩的事情。如今,在LINQ表達式中只須要使用group子句就能夠輕鬆完成對內存對象的分組。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="歐陽錦鵬", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官無忌", Age=23, Tel ="1380524****"} }; //按照名字的前2個字進行分組 var query = from customer in clist group customer by customer.Name.Substring(0, 2); foreach (IGrouping<string,CustomerInfo> group in query) { Console.WriteLine("分組鍵:{0}",group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 電話:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); }
上例代碼,按照form子句創建的範圍變量customer的Name屬性的前兩個字做爲鍵值進行分組。因此TKey的類型是一個字符串類型。
分組鍵:歐陽
姓名:歐陽曉曉 電話:1330708**** 姓名:歐陽錦鵬 電話:1330708**** *************************************** 分組鍵:上官 姓名:上官飄飄 電話:1592842**** 姓名:上官無忌 電話:1380524**** ***************************************
再看一個分組的例子:
//按照年齡是否大於20分組 var query = from customer in clist group customer by customer.Age > 20; foreach (var group in query) { Console.WriteLine("分組鍵:{0}",group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 電話:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); }
group子句用了一個布爾表達式,因此IGrouping<TKey,TElement>的TKey變成了一個bool型。而且循環遍歷的時候能夠用var代替IGrouping的聲明:
foreach (var group in query)
分組鍵:True
姓名:歐陽曉曉 電話:1330708**** 姓名:歐陽錦鵬 電話:1330708**** 姓名:上官無忌 電話:1380524**** *************************************** 分組鍵:False 姓名:上官飄飄 電話:1592842**** ***************************************
5. into子句
into子句做爲一個臨時標識符,用於select,group,join子句中。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="歐陽錦鵬", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官無忌", Age=23, Tel ="1380524****"} }; //按照名字的前兩個字進行分組,再用分組Key進行排序 var query = from customer in clist group customer by customer.Name.Substring(0, 2) into gpcustomer orderby gpcustomer.Key descending select gpcustomer; Console.WriteLine("into 用於group子句"); foreach (var group in query) { Console.WriteLine("分組鍵:{0}", group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 電話:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); } var query2 = from customer in clist select new { NewName = customer.Name, NewAge = customer.Age } into newCustomer orderby newCustomer.NewAge select newCustomer; Console.WriteLine("into 用於select子句"); foreach (var ci in query2) { Console.WriteLine("{0} 年齡:{1}", ci.NewName, ci.NewAge); }
into子句提供了一個臨時標識符,它存儲了into子句前面的查詢內容,使它後面的子句能夠方便的使用,對其進行再次查詢,投影等操做。
執行結果:
into 用於group子句
分組鍵:上官
姓名:上官飄飄 電話:1592842**** 姓名:上官無忌 電話:1380524**** *************************************** 分組鍵:歐陽 姓名:歐陽曉曉 電話:1330708**** 姓名:歐陽錦鵬 電話:1330708**** *************************************** into 用於select子句 上官飄飄 年齡:17 上官無忌 年齡:23 歐陽曉曉 年齡:35 歐陽錦鵬 年齡:35
LINQ能夠按元素的一個或多個屬性對元素進行排序。LINQ表達式的排序方式分爲OrderBy、OrderByDescending、ThenBy、ThenByDescending這四種。
6.1 OrderBy和OrderByDescending
OrderBy用於按元素的值進行升序,語法:
orderby 用於排序的元素的表達式
OrderByDescending用於按元素的值進行降序,語法:
orderby 用於排序的元素的表達式 descending
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="歐陽錦鵬", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官無忌", Age=23, Tel ="1380524****"} }; //按照年齡升序 var query = from customer in clist orderby customer.Age select customer; Console.WriteLine("按年齡升序排列"); foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); } //按照年齡降序 var query2 = from customer in clist orderby customer.Age descending select customer; Console.WriteLine("\n按年齡降序排列"); foreach (var ci in query2) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); }
運行結果:
按年齡升序排列
姓名:上官飄飄 年齡:17 電話:1592842**** 姓名:上官無忌 年齡:23 電話:1380524**** 姓名:歐陽曉曉 年齡:35 電話:1330708**** 姓名:歐陽錦鵬 年齡:35 電話:1330708**** 按年齡降序排列 姓名:歐陽曉曉 年齡:35 電話:1330708**** 姓名:歐陽錦鵬 年齡:35 電話:1330708**** 姓名:上官無忌 年齡:23 電話:1380524**** 姓名:上官飄飄 年齡:17 電話:1592842****
6.2 ThenBy和ThenByDescending
ThenBy和ThenByDescending用於對元素進行次要排序。基本語法:
orderby 用於排序的元素表達式,用於排序的元素表達式 orderby 用於排序的元素表達式,用於排序的元素表達式 descending
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黃蓉", Age=17, Tel ="1300524****"} }; //按照年齡升序,再按名字的字數次要排序 var query = from customer in clist orderby customer.Age,customer.Name.Length select customer; Console.WriteLine("按年齡排列,按名字字數進行次要排序"); foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); } //按照年齡升序,再按名字的字數降序次要排序 var query2 = from customer in clist orderby customer.Age, customer.Name.Length descending select customer; Console.WriteLine("\n按年齡排列,按名字字數進行降序次要排序"); foreach (var ci in query2) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); } //按照年齡升序,再按名字的字數降序要排序,在按電話號碼進行第三條件排序 var query3 = from customer in clist orderby customer.Age, customer.Name.Length,customer.Tel select customer; Console.WriteLine("\n按年齡,名字字數,電話號碼排序"); foreach (var ci in query3) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); }
執行結果:
按年齡排列,按名字字數進行次要排序
姓名:郭靖 年齡:17 電話:1330708**** 姓名:黃蓉 年齡:17 電話:1300524**** 姓名:上官飄飄 年齡:17 電話:1592842**** 姓名:歐陽曉曉 年齡:35 電話:1330708**** 按年齡排列,按名字字數進行降序次要排序 姓名:上官飄飄 年齡:17 電話:1592842**** 姓名:郭靖 年齡:17 電話:1330708**** 姓名:黃蓉 年齡:17 電話:1300524**** 姓名:歐陽曉曉 年齡:35 電話:1330708**** 按年齡,名字字數,電話號碼排序 姓名:黃蓉 年齡:17 電話:1300524**** 姓名:郭靖 年齡:17 電話:1330708**** 姓名:上官飄飄 年齡:17 電話:1592842**** 姓名:歐陽曉曉 年齡:35 電話:1330708****
7. let子句
let子句用於在LINQ表達式中存儲子表達式的計算結果。let子句建立一個範圍變量來存儲結果,變量被建立後,不能修改或把其餘表達式的結果從新賦值給它。此範圍變量能夠再後續的LINQ子句中使用。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黃蓉", Age=17, Tel ="1300524****"} }; //姓「郭」或「黃」的客戶 var query = from customer in clist let g = customer.Name.Substring(0,1) where g == "郭" || g == "黃" select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} 電話:{2}", ci.Name, ci.Age, ci.Tel); }
使用let 創建了個範圍變量,這個範圍變量在後續的where子句中使用,若是不使用let子句,where子句的表達式將寫成這樣:
where customer.Name.Substring(0, 1) == "郭" || customer.Name.Substring(0, 1) == "黃"
姓名:郭靖 年齡:17 電話:1330708**** 姓名:黃蓉 年齡:17 電話:1300524****
8. join子句
若是一個數據源中元素的某個屬性能夠跟另外一個數據源中元素的屬性進行相等比較,那麼這兩個數據源能夠用join子句進行關聯。jion子句用equals關鍵字進行比較,而不是常見的==。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="歐陽曉曉", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飄飄", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黃蓉", Age=17, Tel ="1300524****"} }; List<CustomerTitle> titleList = new List<CustomerTitle> { new CustomerTitle{ Name="歐陽曉曉", Title="歌手"}, new CustomerTitle{ Name="郭靖", Title="大俠"}, new CustomerTitle{ Name="郭靖", Title="洪七公徒弟"}, new CustomerTitle{ Name="黃蓉", Title="才女"}, new CustomerTitle{ Name="黃蓉", Title="丐幫幫主"} }; //根據姓名進行內部聯接 Console.WriteLine("內部聯接"); var query = from customer in clist join title in titleList on customer.Name equals title.Name select new { Name = customer.Name, Age = customer.Age, Title = title.Title }; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年齡:{1} {2}", ci.Name, ci.Age, ci.Title); } //根據姓名進行分組聯接 Console.WriteLine("\n根據姓名進行分組聯接"); var query2 = from customer in clist join title in titleList on customer.Name equals title.Name into tgroup select new { Name = customer.Name, Titles = tgroup }; foreach (var g in query2) { Console.WriteLine(g.Name); foreach (var g2 in g.Titles) { Console.WriteLine(" {0}", g2.Title); } } //根據姓名進行 左外部聯接 Console.WriteLine("\n左外部聯接"); var query3 = from customer in clist join title in titleList on customer.Name equals title.Name into tgroup from subTitle in tgroup.DefaultIfEmpty() select new { Name = customer.Name, Title = (subTitle == null ? "空缺" : subTitle.Title) }; foreach (var ci in query3) { Console.WriteLine("姓名:{0} {1} ", ci.Name, ci.Title); }
要仔細理解上例的,內聯接,分組聯接,以及左聯接。
內部聯接 姓名:歐陽曉曉 年齡:35 歌手 姓名:郭靖 年齡:17 大俠 姓名:郭靖 年齡:17 洪七公徒弟 姓名:黃蓉 年齡:17 才女 姓名:黃蓉 年齡:17 丐幫幫主 根據姓名進行分組聯接 歐陽曉曉 歌手 上官飄飄 郭靖 大俠 洪七公徒弟 黃蓉 才女 丐幫幫主 左外部聯接 姓名:歐陽曉曉 歌手 姓名:上官飄飄 空缺 姓名:郭靖 大俠 姓名:郭靖 洪七公徒弟 姓名:黃蓉 才女 姓名:黃蓉 丐幫幫主
LINQ to Objects是 LINQ的基礎,而 LINQ to SQL、 LINQ to XML是中間 LINQ提供程序,他們主要是把數據源轉換成 LINQ to Objects兼容的類型,以便 LINQ to Objects進行操做。 LINQ to Objects就是直接對IEnumerable或泛型IEnumerable<T>集合進行查詢。LINQ表達式是LINQ標準查詢運算符的一部分,而LINQ標準查詢運算符則是LINQ to Objects的基礎。它們是一組靜態方法,被定義在System.Linq.Enumerable和System.Linq.Queryable類中。這兩個類中方法基本一致,惟一的不一樣點是System.Linq.Queryable類中方法會把LINQ表達式拆解成表達式目錄樹,其餘一些Linq提供程序能夠將這個表達式目錄樹翻譯成查詢語句,好比SQL語句,而後再執行相關操做。
本文主要學習System.Linq.Enumerable的擴展方法,這些方法按照執行的行爲不一樣,能夠分爲延期執行和當即執行。延期執行的運算符在枚舉時被執行,下面要學習的就是延期執行方法的一部分。
1,Take 方法
Take方法用於從一個序列的開頭返回指定數量的元素。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; //直接輸出前3個元素 Console.WriteLine("Take方法直接輸出前3個元素"); foreach (var name in names.Take(3)) { Console.WriteLine(name); } var query = from n in names where n.Length == 2 select n; Console.WriteLine("\nTake方法輸出查詢結果的前1個元素"); foreach (var s in query.Take(1)) { Console.WriteLine(s); }
輸出結果:
Take方法直接輸出前3個元素
郭靖
李莫愁
歐陽曉曉
Take方法輸出查詢結果的前1個元素
郭靖
2,TakeWhile 方法
TakeWhile方法獲取序列中從開頭起符合條件的元素,直到遇到不符合條件的元素爲止的全部元素。條件代理部分有兩種形式:
Func<TSource, bool> predicate
Func<TSource, int, bool> predicate 第二個參數是元素的索引
注意:當條件爲假時,就中止了,後面的元素不會輸出。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; //輸出名字小於4個字的元素 var takeNames = names.TakeWhile(n => n.Length < 4); foreach (var name in takeNames) { Console.WriteLine(name); } Console.WriteLine("\nTakeWhile 帶索引參數"); //輸出名字字數小於等於4 而且索引小於4的元素 foreach (var name in names.TakeWhile((n, i) => n.Length <= 4 && i < 4)) { Console.WriteLine(name); }
輸出結果:
郭靖
李莫愁
TakeWhile 帶索引參數
郭靖
李莫愁
歐陽曉曉
黃蓉
3,Skip 方法
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; //跳過前3個元素 Console.WriteLine("Take方法跳過前3個元素"); foreach (var name in names.Skip(3)) { Console.WriteLine(name); } var query = from n in names where n.Length == 2 select n; Console.WriteLine("\nTake方法跳過查詢結果的前1個元素"); foreach (var s in query.Skip(1)) { Console.WriteLine(s); }
輸出結果:
Take方法跳過前3個元素
黃蓉
黃藥師
Take方法跳過查詢結果的前1個元素
黃蓉
4,SkipWhile 方法
SkipWhile 方法用於只要知足指定的條件,就跳過序列中得元素。
注意:當遇到條件爲假時,就中止跳越了,輸出剩餘的全部元素。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; Console.WriteLine("SkipWhile跳過名字爲2個字的元素"); foreach (var name in names.SkipWhile(n => n.Length == 2)) { Console.WriteLine(name); } Console.WriteLine("\nSkipWhile跳過名字小於4個字,而且索引小於2"); foreach (var s in names.SkipWhile((n, i) => n.Length < 4 && i < 2)) { Console.WriteLine(s); }
輸出結果:
SkipWhile跳過名字爲2個字的元素
李莫愁
歐陽曉曉
黃蓉
黃藥師
SkipWhile跳過名字小於4個字,而且索引小於2
歐陽曉曉
黃蓉
黃藥師
5,Reverse 方法
Reverse 方法用於反轉序列中的元素。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; foreach (var name in names.Reverse()) { Console.WriteLine(name); }
輸出結果:
黃藥師
黃蓉
歐陽曉曉
李莫愁
郭靖
6,Distinct 方法
Distinct 方法用於去除重複元素。
string[] names = { "郭靖", "郭靖", "李莫愁", "歐陽曉曉", "歐陽曉曉", "黃蓉", "黃藥師" }; Console.WriteLine("含有重複元素的數組"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重複元素的數組"); foreach (var name in names.Distinct()) { Console.Write(name + " "); }
輸出結果:
含有重複元素的數組
郭靖 郭靖 李莫愁 歐陽曉曉 歐陽曉曉 黃蓉 黃藥師
去除重複元素的數組
郭靖 李莫愁 歐陽曉曉 黃蓉 黃藥師
自定義IEqualityComparer<T>接口的相等比較器
public class MyEqualityComparer<T> : IEqualityComparer<T> { #region IEqualityComparer<T> 成員 public bool Equals(T x, T y) { string temp = x as string; if (temp != null) { if (temp == "歐陽曉曉") //對"歐陽曉曉"不過濾 return false; } if (x.GetHashCode() == y.GetHashCode()) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } #endregion } private void DistinctDemo() { string[] names = { "郭靖", "郭靖", "李莫愁", "歐陽曉曉", "歐陽曉曉", "黃蓉", "黃藥師" }; Console.WriteLine("含有重複元素的數組"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重複元素的數組,實現自定義IEqualityComparer<T>"); foreach (var name in names.Distinct(new MyEqualityComparer<string>())) { Console.Write(name + " "); } }
輸出結果:
含有重複元素的數組
郭靖 郭靖 李莫愁 歐陽曉曉 歐陽曉曉 黃蓉 黃藥師
去除重複元素的數組,實現自定義IEqualityComparer<T> 郭靖 李莫愁 歐陽曉曉 歐陽曉曉 黃蓉 黃藥師
7,Union 方法
Union 方法 用於合併兩個序列,並去掉重複元素。
string[] names = { "郭靖", "郭靖", "李莫愁", "歐陽曉曉", "歐陽曉曉", "黃蓉", "黃藥師" }; Console.WriteLine("含有重複元素的數組"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重複元素的數組,實現自定義IEqualityComparer<T>"); foreach (var name in names.Distinct(new MyEqualityComparer<string>())) { Console.Write(name + " "); }
輸出結果:
合併後的元素
郭靖 李莫愁 歐陽曉曉 黃蓉 黃藥師 楊過
自定義IEqualityComparer<T>接口的相等比較器
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("合併後的元素"); foreach (var name in names.Union(names2,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
輸出結果:
合併後的元素
郭靖 李莫愁 歐陽曉曉 黃蓉 黃藥師 楊過 歐陽曉曉
8,Concat 方法
Concat 方法 用於鏈接兩個序列,與Union不一樣,它不會過濾重複的元素。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("合併後的元素"); foreach (var name in names.Concat(names2)) { Console.Write(name + " "); }
輸出元素:
合併後的元素
郭靖 李莫愁 歐陽曉曉 黃蓉 黃藥師 郭靖 楊過 歐陽曉曉
9,Intersect 方法
Intersect 方法用於生成兩個序列的交集。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("相交的元素"); foreach (var name in names.Intersect(names2)) { Console.Write(name + " "); }
輸出結果:
相交的元素
郭靖 歐陽曉曉
自定義IEqualityComparer<T>
public class MyEqualityComparer<T> : IEqualityComparer<T> { #region IEqualityComparer<T> 成員 public bool Equals(T x, T y) { string temp = x as string; if (temp != null) { if (temp == "歐陽曉曉") //對"歐陽曉曉"不過濾 return false; } if (x.GetHashCode() == y.GetHashCode()) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } #endregion }
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("相交的元素"); foreach (var name in names.Intersect(names2,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
輸出結果:
相交的元素
郭靖
10,Except 方法
Except 方法用於生成兩個序列的差集。
注意:返回是第一個數組裏,去掉指定數組裏的元素後,剩下的一個序列。
它和Intersect方法不是互補的,不要搞混了。下面的「楊過」就不會輸出。由於它是指定數組裏的元素,和源數組一毛錢關係都沒有。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("2個數組的不一樣元素"); foreach (var name in names.Except(names2)) { Console.Write(name + " "); }
輸出結果:
2個數組的不一樣元素
李莫愁 黃蓉 黃藥師
運用自定義IEqualityComparer<T>指定比較器。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; string[] names2 = { "郭靖", "楊過", "歐陽曉曉" }; Console.WriteLine("2個數組的不一樣元素"); foreach (var name in names2.Except(names,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
輸出結果:
2個數組的不一樣元素
楊過 歐陽曉曉
11,Range 方法
Range 方法用於生成指定範圍的整數序列。在BS程序中,常常須要分頁顯示,在頁面中須要顯示頁面號碼的連接,用這個方法能夠生成頁碼的數組。
因爲沒有this關鍵字,它是一個普通的靜態方法。
int istart = 1;//起始頁碼 int iend = 12; //結束頁碼 var pages = Enumerable.Range(1, iend - istart + 1); Console.WriteLine("輸出頁碼"); foreach (var n in pages) { Console.Write("[{0}] ", n); }
輸出結果:
輸出頁碼
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]
12,Repeat 方法
Repeat 方法用於生成指定數量重複元素的序列。因爲沒有this關鍵字,它是一個普通的靜態方法。
var people = new { Name = "郭靖", Age = 35 };//定義一個匿名類型 var peoples = Enumerable.Repeat(people, 4); Console.WriteLine("包含4個匿名元素:"); foreach (var n in peoples) { Console.WriteLine("{0} {1} ", n.Name, n.Age); }
輸出結果:
包含4個匿名元素:
郭靖 35 郭靖 35 郭靖 35 郭靖 35
13,Empty 方法
Empty 方法用於獲取一個指定類型參數的空序列。因爲沒有this關鍵字,它是一個普通的靜態方法。
var s = Enumerable.Empty<string>(); Console.WriteLine("序列的元素數:{0} ", s.Count());
輸出結果:
序列的元素數:0
14,DefaultIfEmpty 方法
DefaultIfEmpty 方法用於獲取序列,若是序列爲空則添加一個類型的默認值。例如:若是元素爲引用類型,添加null元素;元素爲int類型,則添加int的默認值0。
string[] names = { "郭靖", "李莫愁", "歐陽曉曉", "黃蓉", "黃藥師" }; var intempty = Enumerable.Empty<int>();//空的Int類型序列 //沒有找到元素的序列 var empty = from n in names where n.Length == 5 select n; Console.WriteLine("DefaultIfEmpty 返回有內容的序列"); foreach (var n in names) { Console.Write("{0} ", n); } Console.WriteLine("\nempty空序列元素數:{0}", empty.Count()); Console.WriteLine("empty空序列應用DefaultIfEmpty 後的元素數:{0}", empty.DefaultIfEmpty().Count()); Console.Write("empty空序列應用DefaultIfEmpty 後的元素值:"); foreach (var n in empty.DefaultIfEmpty()) { if (n == null) Console.Write("null"); } Console.WriteLine("\n****************************************"); Console.WriteLine("intempty空序列元素數:{0}", intempty.Count()); Console.WriteLine("intempty空序列應用DefaultIfEmpty 後的元素數:{0}", intempty.DefaultIfEmpty().Count()); Console.Write("intempty空序列應用DefaultIfEmpty 後的元素值:"); foreach (var n in intempty.DefaultIfEmpty()) { Console.Write(n); }
輸出結果:
DefaultIfEmpty 返回有內容的序列
郭靖 李莫愁 歐陽曉曉 黃蓉 黃藥師
empty空序列元素數:0 empty空序列應用DefaultIfEmpty 後的元素數:1 empty空序列應用DefaultIfEmpty 後的元素值:null **************************************** intempty空序列元素數:0 intempty空序列應用DefaultIfEmpty 後的元素數:1 intempty空序列應用DefaultIfEmpty 後的元素值:0
這個方法還能夠指定一個自定義的默認值。
var intempty = Enumerable.Empty<int>();//空的Int類型序列 Console.Write("int 類型自定義默認值:"); foreach (var i in intempty.DefaultIfEmpty(200)) { Console.Write(i); }
輸出結果:
int 類型自定義默認值:200
七、Cast 方法
Cast 方法用於按照TResult類型轉換IEnumerable序列的集合。
//ArrayList沒有實現IEnumerable<T>接口 ArrayList names = new ArrayList(); names.Add("郭靖"); names.Add("李莫愁"); names.Add("歐陽曉曉"); IEnumerable<string> newNames = names.Cast<string>(); foreach (var s in newNames) { Console.WriteLine(s); }
輸出結果:
郭靖
李莫愁
歐陽曉曉
8,OfType 方法
OfType 方法用於根據TResult類型篩選IEnumerable類型序列的元素。它的用途和Cast方法相似,但OfType方法若是遇到不能強制轉換成TResutl的類型,會丟棄該元素,而不會出現運行錯誤。
//ArrayList沒有實現IEnumerable<T>接口 ArrayList names = new ArrayList(); names.Add("郭靖"); names.Add("李莫愁"); names.Add(100); names.Add(new Stack()); names.Add("歐陽曉曉"); IEnumerable<string> newNames = names.OfType<string>(); foreach (var s in newNames) { Console.WriteLine(s); }
輸出結果:
郭靖
李莫愁
歐陽曉曉
9,AsEnumerable方法
AsEnumerable方法根據元素的類型轉換爲泛型IEnumerable<T>類型。
MSDN上的一個例子,AsEnumerable用於隱藏本身定義的和IEnumerable裏的擴展方法同名的方法。
public class MyList<T> : List<T> { public IEnumerable<T> Where(Func<T, bool> predicate) { Console.WriteLine("In MyList of Where"); return Enumerable.Where(this, predicate); } } private void AsEnumerableDemo() { MyList<string> list = new MyList<string>() { "郭靖", "黃蓉", "黃藥師" }; var query1 = list.Where(n => n.Contains("郭")); Console.WriteLine("query1 created"); var query2 = list.AsEnumerable().Where(n => n.Contains("郭")); Console.WriteLine("query2 created"); }
運行結果:
In MyList of Where
query1 created
query2 created
AsEnumerable方法常常用於Linq To SQL查詢,將IQueryable<T>轉換成IEnumerable<T>接口。由於一些擴展方法,如Reverse等,雖然在IQueryable<T>裏有定義,但並不能將其翻譯成對應的SQL語句,因此運行時會報錯。用AsEnumerable方法轉換成IEnumerable<T>後,實際的數據就在內存中操做。關於IQueryable<T>調用AsEnumerable背後的轉換本質,有待進一步考證。
在應用到IEnumberable 和IQueryable兩個接口時,代碼每每很類似,從而形成了不少困惑,而後事實上他們兩是有很大的區別的,各類都有本身特定的使用場景。下面是IEnumberable和IQueryable的屬性對比:
IEnumerable | IQueryable | |
Namespace | System.Collections Namespace | System.Linq Namespace |
繼承於 | No base interface | 繼承於 IEnumerable |
Deferred Execution | 支持 | 支持 |
Lazy Loading | 不支持 | 支持 |
如何工做 | 當從數據庫中查詢數據,IEnumberable在服務器端執行查詢操做,下載數據到客戶端的內存中,而後再篩選數據,所以這個操做須要更多的工做而變得緩慢。 | 當從數據庫中查詢數據,IQueryable在服務器端根據全部的filter條件執行查詢操做,所以該操做須要更少的工做而運行快。 |
適用於 | LINQ to Object and LINQ to XML queries. | LINQ to SQL queries. |
自定義查詢 | 不支持 | 支持使用CreateQuery 和Execute 方法。 |
Extension mehtod parameter |
Extension methods supported in IEnumerable takes functional objects. | Extension methods supported in IEnumerable takes expression objects i.e. expression tree. |
使用場合 | 當從內存中的數據集合(如LIst,Array etc)查詢數據的時候 | 當查詢非內存中的數據集合(如遠程數據庫,service等)時。 |
最常使用 | 內存遍歷 | Paging |
參考資料:http://www.cnblogs.com/xiashengwang/archive/2012/07/29/2613940.html