一、什么是Linq
Linq是語言集成查詢(Language Integrated Query)的簡稱,是visual Studio 2008和.NET Framework 3.5版本中一項突破性的創新,它在對象領域和數據領域之間架起了一座橋梁。
Linq支持各種數據源:
1、ADO.NET DataSet
2、XML文檔
3、SQL Server數據庫
4、支持IEnumerable或泛型IEnumerable(T)接口的任意對象集合
5、更多。。。
二、Linq的優點
- 傳統的SQL查詢
select FirstName,LastName,* from Customers where city = 'Shanghai'
order by district
簡單的字符串表示,沒有編譯時類型檢查,沒有IDE的智能感知支持。
以上例子只是針對SQL,針對不同的數據源,例如XML文檔、各種WEB服務等我們還要學習不同的查詢方法。
- Linq查詢示例
完全類型檢查和IDE智能感知支持。
三、Linq查詢的步驟
所有的Linq查詢操作都由以下三個不同的操作組成:
- 獲得數據源
- 創建查詢
- 執行查詢
數據源
- 想要使用Linq進行查詢,數據源必須支持IEnumerable或IEnumerable(T)泛型接口或派生接口(如泛型的IQueryable(T)接口)。
查詢
- 查詢指定要從數據源中檢索的信息
- 查詢還可以指定在返回這些信息之前如何對其進行排序、分組和結構化
- 查詢存儲在查詢變量中,並用查詢表達式進行初始化。為使編寫查詢的工作更加容易,C#引入了新的查詢語法
查詢執行
- 查詢變量本身只是存儲查詢命令。實際的查詢執行會延遲在foreach語句中循環訪問變量時發生。此概念稱為“延遲執行”。
- 強制立即執行,可以通過以下兩個方法,使得Linq立即執行查詢
執行聚合函數(Count、Max、Average、First)。
調用ToList(<T>Source)或ToArray(<T>Source)方法緩存結果。
四、查詢基本操作
from子句
用於獲取數據源
var queryAllCustomers=
from cust in Customers select cust;
- 查詢表達式必須以from子句開頭
- 例子中cust是范圍變量,范圍變量類似於foreach循環中的迭代變量,但在查詢表達式中,實際上不發生迭代。執行查詢時,范圍變量將用作對Customers中的每個后續元素的引用。因為編譯器可以推斷cust的類型,所以不必顯示指定此類型。
- Customers是數據源,實現了IEnumerable或IEnumerable(T)或其派生接口的。
復合from子句
在某些情況下,原序列中的每個元素本身可能是序列,也可能包含序列。用於訪問某個數據源中的內部集合。
需求:查詢出成績有90分以上的學生,得到他們的名字和成績
//數據源
IList<Student> students = new List<Student> { new Student{ Name="Kevin", Score=new List<int>{89,93,88,78}}, new Student{ Name="Jackie",Score=new List<int>{92,87,83,91}}, new Student{ Name="Helen",Score=new List<int>{53,76,72,62}} }; //使用復合from子句查詢命令
var getStudent =
from student in students from score in student.Score where score > 90
select new { Name=student.Name,Score=score};
分析:從代碼中,我們可以看到學生對象中有個Score屬性,Score屬性本身就是List集合,這時候我們就要用到復合from子句進行查詢了。首先遍歷學生對象集合中的每個學生對象,然后在用另一個from子句,對每個學生對象中的Score屬性進行遍歷,篩選出含有90分以上的學生信息進行返回。
使用let子句擴展范圍變量
用於創建查詢自身的范圍變量
需求:將字符串數組中的兩句英文語句中所有的元音字母打頭的單詞輸出到控制台
string[] strings ={ "I am a new Student.", "You are a talent" }; var query = from sentences in strings let words = sentences.Split(' ') from word in words let w = word.ToLower() where w[0] == 'a' || w[0] == 'e' || w[0] == 'i' || w[0] == 'o' || w[0] == 'u'
select word; foreach (var word in query) { Console.Write(word + ","); }
分析:首先遍歷字符串數組中的每個字符串,用let子句創建查詢自身的范圍變量words,並調用Split(' ')方法,將每個字符串中以空格分割為單詞存入words變量中,然后再次使用let子句創建查詢自身的范圍變量word,並調用ToLower()方法,將每個單詞都變為小寫,最后篩選出首字母為元音的單詞進行返回。
說明:let語句是重命名。let位於第一個from和select語句之間。
這個例子從聯接投影出最終“Let”表達式:
var q =
from c in db.Customers join o in db.Orders on c.CustomerID equals o.CustomerID into ords let z = c.City + c.Country from o in ords select new { c.ContactName, o.OrderID, z };
where子句
將一個布爾條件("謂詞")應用於每個源元素(由范圍變量引用),並返回滿足指定條件的元素。
適用場景:實現過濾,查詢等功能。
說明:與SQL命令中的Where作用相似,都是起到范圍限定也就是過濾作用的,而判斷條件就是它后面所接的子句。
Where操作包括3種形式,分別為簡單形式、關系條件形式、First()形式。下面分別用實例舉例下:
1)簡單形式:
例如:使用where篩選在倫敦的客戶
var q =
from c in db.Customers where c.City == "London"
select c;
再如:篩選1994 年或之后雇用的雇員:
var q =
from e in db.Employees where e.HireDate >= new DateTime(1994, 1, 1) select e;
2)關系條件形式:
篩選庫存量在訂貨點水平之下但未斷貨的產品:
var q =
from p in db.Products where p.UnitsInStock <= p.ReorderLevel && !p.Discontinued select p;
篩選出UnitPrice 大於10 或已停產的產品:
var q =
from p in db.Products where p.UnitPrice > 10m || p.Discontinued select p;
下面這個例子是調用兩次where以篩選出UnitPrice大於10且已停產的產品。
var q = db.Products.Where(p=>p.UnitPrice > 10m).Where(p=>p.Discontinued);
3)First()形式:
返回集合中的一個元素,其實質就是在SQL語句中加TOP (1)。
簡單用法:選擇表中的第一個發貨方。
Shipper shipper = db.Shippers.First();
元素:選擇CustomerID 為“BONAP”的單個客戶
Customer cust = db.Customers.First(c => c.CustomerID == "BONAP");
條件:選擇運費大於 10.00 的訂單:
Order ord = db.Orders.First(o => o.Freight > 10.00M);
其他示例代碼:
需求:將數組中小於5的偶數查詢出來輸出到控制台
//數據源
int[] arr = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 }; //使用Where子句查詢的查詢語句
var query = from a in arr where a < 5 && a % 2 == 0
select a; //執行查詢
foreach (var a in query) { Console.WriteLine(a); }
分析:首先遍歷數組中的每個元素,然后用where語句篩選出小於5,並且對2去模是0的數查詢出來返回。
where子句不僅能使用表達式來進行篩選,還可以使用方法進行篩選。
public static bool IsEven(int a) { return a % 2 == 0 ? true : false; }
//數據源
int[] arr = { 0, 3, 2, 1, 9, 6, 8, 7, 4, 5 }; //where子句也可以接受一個方法
var query = from a in arr where IsEven(a) select a; foreach (var a in query) { Console.Write(a + ","); }
這就是一個在where語句中使用方法進行篩選的例子,輸出的結果和上例完全一樣。
使用where子句還要注意以下幾點
- 一個查詢表達式可以包含多個where子句
- where子句是一種篩選機制。除了不能是第一個或最后一個子句外,它幾乎可以放在查詢表達式中的任何位置。where子句可以出現在group子句的前面或后面,具體情況取決於是必須在對源元素進行分組之前還是分組之后來篩選源元素。
- 如果指定的謂詞對於數據源中的元素無效,則會發生編譯時錯誤。這是Linq提供的強類型檢查的一個優點。
- 編譯時,where關鍵字會被轉換為對where標准查詢運算符方法的調用。
orderby子句
適用場景:對查詢出的語句進行排序,比如按時間排序等等。
說明:按指定表達式對集合排序;延遲,:按指定表達式對集合排序;延遲,默認是升序,加上descending表示降序,對應的擴展方法是OrderBy和OrderByDescending
- 對查詢出來的結果集進行升序或降序排列。
- 可以指定多個鍵,以便執行一個或多個次要排序操作。
- 默認排序順序為升序。
- 編譯時,orderby子句將被轉換為對OrderBy方法的調用。orderby子句中的多個鍵轉換為ThenBy方法調用。
接着上個例子的演示,本例是一個升序的排序。
var query = from a in arr where IsEven(a) orderby a ascending select a;
本例是一個降序的排序。
var query = from a in arr where IsEven(a) orderby a descending select a;
1)簡單形式
這個例子使用 orderby 按雇用日期對雇員進行排序:
var q =
from e in db.Employees orderby e.HireDate select e;
說明:默認為升序
2)帶條件形式
注意:Where和Order By的順序並不重要。而在T-SQL中,Where和Order By有嚴格的位置限制。
var q =
from o in db.Orders where o.ShipCity == "London"
orderby o.Freight select o;
語句描述:使用where和orderby按運費進行排序。
3)降序排序
var q =
from p in db.Products orderby p.UnitPrice descending select p;
4)ThenBy
語句描述:使用復合的 orderby 對客戶進行排序,進行排序:
var q =
from c in db.Customers orderby c.City, c.ContactName select c;
說明:按多個表達式進行排序,例如先按City排序,當City相同時,按ContactName排序。這一句用Lambda表達式像這樣寫:
var q = db.Customers .OrderBy(c => c.City) .ThenBy(c => c.ContactName).ToList();
在T-SQL中沒有ThenBy語句,其依然翻譯為OrderBy,所以也可以用下面語句來表達:
var q = db.Customers .OrderBy(c => c.ContactName) .OrderBy(c => c.City).ToList();
所要注意的是,多個OrderBy操作時,級連方式是按逆序。 對於降序的,用相應的降序操作符替換即可。
var q = db.Customers .OrderByDescending(c => c.City) .ThenByDescending(c => c.ContactName).ToList();
需要說明的是,OrderBy操作,不支持按type排序,也不支持匿名類。比如
var q = db.Customers .OrderBy(c => new { c.City, c.ContactName }).ToList();
會被拋出異常。錯誤是前面的操作有匿名類,再跟OrderBy時,比較的是類別。比如
var q = db.Customers .Select(c => new { c.City, c.Address }) .OrderBy(c => c).ToList();
如果你想使用OrderBy(c => c),其前提條件是,前面步驟中,所產生的對象的類別必須為C#語言的基本類型。比如下句,這里City為string類型。
var q = db.Customers .Select(c => c.City) .OrderBy(c => c).ToList();
5)ThenByDescending
這兩個擴展方式都是用在OrderBy/OrderByDescending之后的,第一個ThenBy/ThenByDescending擴展方法作為第二位排序依據,第二個ThenBy/ThenByDescending則作為第三位排序依據,以此類推
var q =
from o in db.Orders where o.EmployeeID == 1
orderby o.ShipCountry, o.Freight descending select o;
語句描述:使用orderby先按發往國家再按運費從高到低的順序對 EmployeeID 1 的訂單進行排序。
6)帶GroupBy形式
var q =
from p in db.Products group p by p.CategoryID into g orderby g.Key select new { g.Key, MostExpensiveProducts =
from p2 in g where p2.UnitPrice == g.Max(p3 => p3.UnitPrice) select p2 };
語句描述:使用orderby、Max 和 Group By 得出每種類別中單價最高的產品,並按 CategoryID 對這組產品進行排序。
group子句
- group子句返回一個IGrouping(T Key,T element)對象序列
- 編譯時,group子句被轉換成對GroupBy方法的調用
需求:根據首字母分組,並打印到控制台
//數據源
string[] fruits = { "apple", "banana", "peach", "orange", "melon", "lemon" }; //分組查詢的查詢語句
var query = from f in fruits group f by f[0]; //執行查詢
foreach (var letters in query) { Console.WriteLine("words that start with letter:" + letters.Key); foreach (var word in letters) { Console.WriteLine(word); } }
分析:首先遍歷字符串數組中的每個字符串,然后根據每個字符串的首字母進行分組,返回結果
var query = from f in fruits group f by f[0] into g where g.Key == 'p' || g.Key == 'b'
select g;
如果您想要對每個組執行附加查詢操作,則可以使用into上下文關鍵字指定一個臨時標識符。使用into時,必須繼續編寫該查詢,並最終用一個select語句或另一個group子句結束該查詢。
多字段分組示例
GroupBy(x => new { x.a , x.b, x.c }).Select( x=> ( new Class名 { a=x.Key.a , b=x.Key.b , c = x.Key.c } ))
join子句
- 使用join子句可以將來自不同源序列並且在對象模型中沒有直接關系的元素相關聯。
- 唯一的要求是每個源中的元素需要共享某個可以進行比較以判斷是否相等的值。
- join子句使用特殊的equals關鍵字比較指定的鍵是否相等。
1)內部連接
var innerJoinQuery =
from category in categories join prod in products on category.ID equals prod.CategoryID select new { ProductName = prod.Name, Category = category.Name };
2)分組連接
var innerGroupJoinQuery =
from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup select new { CategoryName = category.Name, Products = prodGroup };
3)左外部連接
var leftOuterJoinQuery =
from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup from item in prodGroup.DefaultIfEmpty(new Product{Name =
string.Empty, CategoryID = 0}) select new { CatName = category.Name, ProdName = item.Name };
在左外連接中,將返回左側源序列中的所有元素,即使它們在右側序列中沒有匹配的元素也是如此。
若要在Linq中執行左外連接,請將DefaultIfEmpty方法與分組連接結合起來,以指定要在某個元素不具有匹配元素時產生的默認右側元素,可以使用null作為任何引用類型的默認值。也可以指定用戶定義的默認類型。
equals關鍵字
- join子句執行同等連接。換句話說,只能基於兩個鍵之間的相等關系進行匹配。
- 為了表明所有連接都是同等連接,join子句使用equals關鍵字而不是==運算符
select子句(選擇、投影)
select子句可以指定將在執行查詢時產生的值的類型。該子句的結果將基於前面所有子句的計算結果以及select子句本身中的所有表達式。
查詢表達式必須以select子句或group子句結束
在最簡單的情況下,select子句僅指定范圍變量。這會使返回的序列包含於數據源具有相同類型的元素。