新聞中心
LINQ對于開發(fā)人員來說很方便,不過掌握LINQ的標(biāo)準(zhǔn)查詢操作符是基礎(chǔ)中的基礎(chǔ)。本文將為大家詳細(xì)講解LINQ標(biāo)準(zhǔn)查詢操作符的用法,希望對大家有所幫助。語言集成查詢 (LINQ) 允許開發(fā)人員通過強(qiáng)類型化語法使用 Microsoft? .NET Framework 3.5 代碼編寫類似 SQL 的查詢。然后,各種 LINQ 提供程序,如 LINQ to Objects(可利用它根據(jù)對象層次結(jié)構(gòu)編寫查詢)和 LINQ to Entities(可利用它根據(jù)實(shí)體框架的概念模型編寫查詢)可根據(jù)代表數(shù)據(jù)存儲的細(xì)微差別來有效處理這些查詢。

#T#
除強(qiáng)類型化語法外,LINQ 查詢還具有一個(gè)標(biāo)準(zhǔn)查詢操作符庫來增強(qiáng)其功能。這些標(biāo)準(zhǔn)查詢操作符對序列進(jìn)行運(yùn)算并可執(zhí)行各種運(yùn)算,如確定序列中是否存在某個(gè)值以及對序列運(yùn)行合計(jì)函數(shù)(如求和)。
在本月的專欄中,我將使用 LINQ 來執(zhí)行實(shí)際的查詢和運(yùn)算(會(huì)用到 LINQ to Objects 和 LINQ to Entities)。我將查詢一個(gè)實(shí)體集合并使用其導(dǎo)航屬性深入研究一組具備層次結(jié)構(gòu)的實(shí)體。我還會(huì)為您演示如何對數(shù)組和集合應(yīng)用多個(gè)標(biāo)準(zhǔn)查詢操作符。并展示如何使用 lambda 表達(dá)式強(qiáng)化 LINQ 的標(biāo)準(zhǔn)查詢操作符,以及如何利用它們來從序列解析特定信息并對序列執(zhí)行復(fù)雜的邏輯運(yùn)算。本專欄的下載中提供有所有代碼示例(請參見 msdn.microsoft.com/msdnmag/code08.aspx)。
操作符和 LINQ
LINQ 自身功能非常強(qiáng)大,無論使用的是 LINQ to XML、LINQ to DataSets、LINQ to Entities、LINQ to Objects 還是附帶的任何其他 LINQ 提供程序。LINQ 的核心功能在于其強(qiáng)類型化查詢語法,它可用于任意此類提供程序。當(dāng)將 LINQ 與一個(gè)或多個(gè)標(biāo)準(zhǔn)查詢操作符結(jié)合使用時(shí),會(huì)得到一個(gè)功能更為強(qiáng)大的工具集,從而可精細(xì)地控制一組數(shù)據(jù)。
標(biāo)準(zhǔn)查詢操作符在 System.Linq 命名空間中的 System.Core.dll 程序集中作為靜態(tài)類 Enumerable 和 Queryable 的擴(kuò)展方法存在,并且可用于實(shí)現(xiàn) IEnumerable 或 IQueryable 的對象。這樣它們就能使用 LINQ to Entities 和 LINQ to SQL 之類的提供程序?qū)Ω黝悓ο髨?zhí)行運(yùn)算,從內(nèi)存中的集合和數(shù)組(序列)到遠(yuǎn)程數(shù)據(jù)庫。
可輕松地確定處理特定任務(wù)時(shí)所擁有的操作符。如果要在 LINQ 查詢中使用操作符,可使用 Queryable 靜態(tài)類可用擴(kuò)展方法中的操作符。如果要對實(shí)現(xiàn) IEnumerable 的序列使用操作符,可使用 Enumerable 靜態(tài)類中的一個(gè)擴(kuò)展方法。但是,請記?。翰⒎?Queryable 類中的所有操作符都適用于基礎(chǔ)數(shù)據(jù)存儲,因此運(yùn)行時(shí)可能不支持某些操作符。
操作符類型
操作符有多種類型(使用對象瀏覽器查看 Enumerable 和 Queryable 類即可找到所有操作符)。以字母順序顯示了不同類型操作符的分類。可利用它來大致了解一下操作符所提供的功能。我將使用 LINQ to Objects 和 LINQ to Entities 展示一小組此類操作符,以顯示它們?nèi)绾螢閷?shí)際應(yīng)用程序帶來好處。
FigureACategories of Operators
Lambda 表達(dá)式
許多標(biāo)準(zhǔn)查詢操作符在對序列執(zhí)行運(yùn)算時(shí)都使用 Func 委托來處理單個(gè)元素。Lambda 表達(dá)式可與標(biāo)準(zhǔn)查詢操作符結(jié)合使用以代表委托。lambda 表達(dá)式是創(chuàng)建委托實(shí)現(xiàn)的簡略表達(dá)形式,并可用于匿名委托適用的所有場合。C# 和 Visual Basic? .NET 均支持 Lambda 表達(dá)式。但是,必須注意:由于 Visual Basic .NET 尚不支持匿名方法,Lambda 表達(dá)式可能僅包含一個(gè)語句。
讓我們來看看如何對一個(gè)整數(shù)數(shù)組使用 Single 操作符。這個(gè)整數(shù)數(shù)組的每個(gè)元素代表 2 的 1 到 10 次方。先創(chuàng)建此數(shù)組,然后使用 Single 操作符來檢索滿足 Lambda 表達(dá)式中指定條件的單個(gè)整數(shù)元素:
- int[] nums = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
- int singleNum = nums.Single(x => x > 16 && x < 64);
- Console.WriteLine(singleNum.ToString());
Lambda 表達(dá)式包含多個(gè)關(guān)鍵部分。Lambda 表達(dá)式首先定義傳入委托的變量。在以上代碼示例中,x(在 => 操作符左側(cè)聲明)是參數(shù),代表傳遞給它的 nums 數(shù)組中的每個(gè)元素。Lambda 表達(dá)式的剩余部分代表數(shù)組中每個(gè)元素的評估邏輯??墒褂媚涿休p松地重新編寫以上表達(dá)式,如下所示:
- int singleNum = nums.Single(
- delegate(int x) {return (x > 16 && x < 64); }
- ) ;
但是,此代碼的可讀性不及 Lambda 表達(dá)式。C# 2.0 引入了可使委托的傳遞稍微輕松些的匿名委托;但是,Lambda 表達(dá)式的簡潔語法可使其更加簡單。
First 和 Single
如果必須從序列中提取一個(gè)值,F(xiàn)irst、FirstOrDefault、Single 和 SingleOrDefault 操作符都非常有用。First 方法返回序列中的***個(gè)元素。First 有一個(gè)重載方法,可使用它來傳入 Lambda 表達(dá)式來代表一個(gè)條件。例如,如果要返回整數(shù)序列中整數(shù)元素大于 50 的***個(gè)元素,可使用以下代碼示例:
- int[] nums = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 };
- int num1 = nums.First();
- int num2 = nums.First(x => x > 50);
- int num3 = nums.FirstOrDefault(x => x > 5000);
- Console.WriteLine(
- num1.ToString() + "-" +
- num2.ToString() + "-" +
- num3.ToString());
此代碼會(huì)查找***個(gè)元素 (1)、大于 50 的***個(gè)元素 (64) 以及大于 5,000 的***個(gè)元素。由于數(shù)組中沒有元素滿足第三個(gè) Lambda 表達(dá)式(數(shù)組中無整數(shù)大于 5,000),則如果代碼使用的是 First 操作符而非 FirstOrDefault,則會(huì)引發(fā)異常。在使用 FirstOrDefault 操作符時(shí),如果沒有元素滿足 Lambda 表達(dá)式,則會(huì)返回 0。First 操作符也可用于 LINQ to Entities 查詢,如下所示:
- using (Entities entities = new Entities())
- {
- var query = (from c in entities.Customers
- select c).First(c => c.City.Equals("London"));
- Console.WriteLine(query.CompanyName);
- }
在此示例中,將返回 London 城中的***個(gè)客戶。正如您所看到的,當(dāng) First 方法用于各種 LINQ 提供程序(在本例中為 LINQ to Objects 和 LINQ to Entities)時(shí),所用的語法并不會(huì)更改。
在 LINQ to Entities 上下文中,F(xiàn)irst 操作符非常有用,尤其是您知道會(huì)從查詢返回單個(gè)記錄時(shí)。例如,您可能有個(gè)查詢,它常在給出 CustomerID 時(shí)獲取一條客戶記錄。這種情況總是返回 0 或 1 條記錄,因此,得到一個(gè)序列不如就得到一個(gè)實(shí)體本身。換句話說,您寧愿獲取 Customer 實(shí)體而非 1 個(gè) Customer 實(shí)體序列。First 方法在某種怦下非常有用,如以下代碼段所示。(由于實(shí)體框架不會(huì)嘗試在客戶端和服務(wù)器之間分發(fā)單個(gè)查詢的執(zhí)行,并且 LINQ to Entities 不支持 Single 方法,因此使用 First 方法是個(gè)輕松的替代方法。)
- using (Entities entities = new Entities())
- {
- var query = (from c in entities.Customers
- where c.CustomerID.Equals("BOLID")
- select c).First();
- Console.WriteLine(query.CompanyName);
- }
聚合、層次結(jié)構(gòu)和投影
在 LINQ to Entities 查詢中使用聚合操作符(如 Sum)可有助于簡化查詢。例如,以下代碼檢索訂單總額大于 $10,000 的一個(gè)訂單序列:
- using (Entities entities = new Entities())
- {
- var query = from o in entities.Orders
- where o.OrderDetails.Sum(
- od => od.UnitPrice * od.Quantity) >= 10000
- select o;
- foreach (Orders order in query)
- Console.WriteLine(order.OrderID);
- }
由于 LINQ 可查詢層次結(jié)構(gòu)實(shí)體集合,因此標(biāo)準(zhǔn)查詢操作符也可用于對嵌套實(shí)體序列執(zhí)行運(yùn)算。當(dāng)必須計(jì)算或詢問派生數(shù)據(jù)時(shí),這一點(diǎn)非常有用。派生數(shù)據(jù)可能僅存在于其基本窗體中,如客戶訂單的詳細(xì)信息僅包含單價(jià)和數(shù)量值。在本例中,未在模型中的任何位置提供代表訂單總金額的聚合數(shù)據(jù)。然而,通過在 LINQ 查詢中應(yīng)用 Sum 操作符,仍可檢索消費(fèi)金額超過 $20,000 的所有客戶,如下所示:
- using (Entities entities = new Entities())
- {
- var query = from c in entities.Customers
- where c.Orders.Sum(
- o => o.OrderDetails.Sum(
- od => od.UnitPrice * od.Quantity)) >= 25000
- select c;
- foreach (Customers customer in query)
- Console.WriteLine(customer.CompanyName);
- }
此示例展示了如何在 LINQ 查詢的多個(gè)層次應(yīng)用標(biāo)準(zhǔn)查詢操作符。查詢最終會(huì)返回一個(gè) Customers 實(shí)體序列,但為達(dá)到此目的,它必須首先深入每個(gè)客戶的訂單以及每個(gè)訂單的訂單詳細(xì)信息獲取所需數(shù)據(jù),這樣才可以計(jì)算每項(xiàng)的價(jià)格,匯總每個(gè)訂單的項(xiàng)目,然后匯總每個(gè)客戶的總額。
Count 操作符是另一聚合標(biāo)準(zhǔn)查詢操作符??赏ㄟ^使用以下代碼確定有多少客戶的消費(fèi)金額超過 $25,000:
- using (Entities entities = new Entities())
- {
- var query = (from c in entities.Customers
- where c.Orders.Sum(
- o => o.OrderDetails.Sum(
- od => od.UnitPrice * od.Quantity)) >= 25000
- select c).Count();
- Console.WriteLine(query);
- }
可使用 Max 操作符來確定***客戶。以下代碼示例將返回消費(fèi)***的客戶所花費(fèi)的金額。它在層次結(jié)構(gòu)的多個(gè)層級中組合使用 Sum 和 Max 聚合操作符:
- using (Entities entities = new Entities())
- { var query = (from c in entities.Customers
- where c.Orders.Sum( o => o.OrderDetails.Sum( od => od.UnitPrice * od.Quantity)) >= 25000
- select c).Count(); Console.WriteLine(query); }
投影和排序
您可能還注意到我在之前的示例中暗藏了一個(gè)投影。在使用 Max 操作符之前,LINQ 查詢并不返回客戶列表。而是會(huì)返回一個(gè)投影,此投影創(chuàng)建了包含 CustomerID 屬性和 Total 屬性(客戶的整個(gè)消費(fèi)金額)的一個(gè)新實(shí)體。投影是 LINQ 必不可少的一部分,如前一示例所示,將它們投影到序列中后,就可使用標(biāo)準(zhǔn)查詢操作符來進(jìn)一步處理它們。
顯示了如何創(chuàng)建一個(gè)新實(shí)體投影,其中包含 CustomerID 和客戶的訂單總金額(使用之前討論的 Sum 操作符)。還使用 OrderByDescending 操作符來按計(jì)算總額對投影實(shí)體序列進(jìn)行排序。如果兩個(gè)客戶總額相同,還會(huì)使用另一排序操作符來進(jìn)一步定義順序。例如,還可使用以下代碼修正的 foreach 語句以進(jìn)一步限定排序規(guī)則:
- Figure1Aggregates, Projections, and Ordering
- using (Entities entities = new Entities())
- {
- var query = from c in entities.Customers
- where c.Orders.Sum(
- o => o.OrderDetails.Sum(od => od.UnitPrice)) > 0
- select new
- {
- c.CustomerID,
- Total = c.Orders.Sum(
- o => o.OrderDetails.Sum(od => od.UnitPrice))
- };
- foreach (var item in query.OrderByDescending(x => x.Total))
- Console.WriteLine(item.CustomerID + " == " + item.Total);
- }
- foreach (var item in
- query.OrderByDescending(x => x.Total)
- .ThenBy(x => x.CustomerID))
- {
- Console.WriteLine(item.CustomerID + " == " + item.Total);
- }
在該代碼段中,我添加了 ThenBy 操作符和一個(gè) Lambda 表達(dá)式,以表示序列應(yīng)首先按 Total 屬性降序排列,然后按投影的 CustomerID 屬性升序排列。
限定符和轉(zhuǎn)換
如果需要確定序列中是否存在某個(gè)值,可使用標(biāo)準(zhǔn)查詢操作符 Any。限定符(如 Any、All 和 Contains)會(huì)搜索元素序列,并評估序列是否滿足 lambda 表達(dá)式的條件。如果需檢查序列以確定某些事宜(例如:是否存在來自特定地址的客戶、所有客戶是否來自同一國家或者任意其他分析確定性問題),它將非常有用。
例如,以下 LINQ 查詢會(huì)檢查是否來自 United Kingdom 的所有客戶都位于 London。它使用限定符 All 并將其傳遞給僅評估城市是否為 London 的 lambda 表達(dá)式。如果序列中的每個(gè)元素都滿足此條件并且 lambda 表達(dá)式返回 true,然后 All 操作符會(huì)返回 true:
- using (Entities entities = new Entities())
- {
- bool allUKCustomerAreFromLondon = (from c in entities.Customers
- where c.Country == "UK"
- select c).All(
- c => c.City.Equals("London"));
- Console.WriteLine(allUKCustomerAreFromLondon ? "Yes" : "No");
- }
需在此查詢中詢問的另一問題是序列中是否有來自 United Kingdom 的 Cowes 的實(shí)體。對于此問題,可使用 Any 限定符來計(jì)算序列,如下所示:
- using (Entities entities = new Entities())
- {
- bool isOneUKCustomerFromCowes = (from c in entities.Customers
- where c.Country == "UK"
- select c).Any(
- c => c.City.Equals("Cowes"));
- Console.WriteLine(isOneUKCustomerFromCowes? "Yes" : "No");
- }
Contains 操作符在評估序列中是否包括您所查找的項(xiàng)目時(shí)類似于 Any 操作符。Any 操作符可確定序列的某個(gè)項(xiàng)中是否存在某個(gè)值,而 Contains 操作符則確定序列中是否存在特定項(xiàng)目實(shí)例。例如,在將某個(gè)對象添加到序列中之前,您可能希望確保序列中并未包含該對象。展示了如何檢查。
Figure2Using Contains and Conversion
- using (Entities entities = new Entities())
- {
- Customers customerBSBEV = (from c in entities.Customers
- where c.CustomerID == "BSBEV"
- select c).First();
- var customersUK = from c in entities.Customers
- where c.Country == "UK"
- select c;
- bool isCustomerInSequence = customersUK.Contains(customerBSBEV);
- Console.WriteLine(isCustomerInSequence? "Yes" : "No");
- }
請注意:首先針對 BSBEV 客戶檢索 Customers 實(shí)體。然后,檢索客戶來自 United Kingdom 的 Customers 實(shí)體序列。***,使用 Contains 操作符來檢查 Customers 序列是否包含 customerBSBEV 變量的實(shí)例。
顯示的 Contains 操作符實(shí)現(xiàn)適用于可基于其實(shí)際實(shí)例信心十足地比較對象的場合。但是,如果需要 Contains 操作符根據(jù)邏輯標(biāo)識進(jìn)行測試又該如何呢?幸運(yùn)的是,Contains 操作符包含一個(gè)重載,可使用它來傳遞實(shí)現(xiàn) IEqualityComparer 接口的對象。要根據(jù) CustomerID 使用 Contains,可按如下所示重新編寫的代碼:
- using (Entities entities = new Entities())
- {
- ...
- bool isCustomerInSequence = customersUK.Contains(customerBSBEV, new CustomerComparer());
- Console.WriteLine(isCustomerInSequence? "Yes" : "No");
- }
其中 CustomerComparer 定義為
- private class CustomerComparer : IEqualityComparer
- {
- public bool Equals(Customers x, Customers y) {
- if (x == null || y == null)
- return false;
- return x.CustomerID.Equals(y.CustomerID);
- }
- ...
- }
結(jié)束語
有許多標(biāo)準(zhǔn)查詢操作符均可定義為 Enumerable 和 Queryable 序列類的擴(kuò)展方法。考試大提示如之前所示,這些操作符有助于擴(kuò)展 LINQ 的功能。我還展示了結(jié)合使用多個(gè) .NET Framework 3.5 新增強(qiáng)功能(包括 lambda 表達(dá)式、LINQ、實(shí)體框架和隱式類型化變量)來更加輕松地編寫功能強(qiáng)大的代碼和邏輯。
分享標(biāo)題:詳解LINQ標(biāo)準(zhǔn)查詢操作符
網(wǎng)頁URL:http://m.5511xx.com/article/dhijcjo.html


咨詢
建站咨詢
