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

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


咨詢
建站咨詢
