前言
LINQ(Language Integrated Query,語言集成查詢)是一組用於C#和VB.NET語言的擴展,它允許編寫C#或者VB.NET代碼,以與查詢數據庫相同的方式操作內存數據。LINQ提Entity Framework技術系列之7:LINQ to Entities供了豐富的類似SQL的查詢語法,功能強大且容易上手。下圖匯總展示了LINQ技術的官方實現集合:
圖1官方LINQ實現匯總圖
正如上圖所示,LINQ to Entities 是LINQ技術在實體對象模型中的一種實現,它與LINQ to SQL以及LINQ to DataSets一起構成了LINQ to ADO.NET。LINQ to Entities可以生成eSQL,並支持使用LINQ語法對實體框架服務層進行查詢。下圖展示了LINQ to Entities消費實體數據模型的技術細節:
圖2 LINQ to Entities與實體數據模型對應圖
相關概念
在開始了解LINQ to Entities之前,需要先對.NET Framework 3.5版本后對C#語言的幾個擴展特性做一些闡釋,這有助於我們更容易、更深刻的理解LINQ to Entities技術的原理與實現。
一、隱式實例化局部變量
.NET Framework 3.5后,實例化局部變量語法有了新的選擇:使用“var”關鍵字隱式類型化:
var user = new User();
隱式類型化語法與Javascript語言里聲明變量的語法類似,它可以一定程度上簡化實例化局部變量的語法。但如果隱式實例化的作用僅限於此,那本文不會專門提及。隱式類型化更重要的用途是用於匿名類型的實例化。
需要提醒注意的是,只有局部變量實例化允許使用隱式實例化語法。這表示私有變量實例化無法享受該福利;同時,僅聲明變量或實例化對象為null也是不允許使用隱式實例化語法的。
二、對象初始化
對象初始化指在實例化對象時,即可對對象的屬性進行賦值:
1 var user = new User() 2 { 3 ID = Guid.NewGuid(), 4 Account = "Apollo" 5 };
三、匿名類型
LINQ to Entities的Select可以將實體類型投影為匿名類型,所以有必要對匿名類型作簡單介紹。匿名類型指的是不顯示聲明類型的細節,而是根據上下文環境需要,臨時聲明滿足需要的類型。由於該類型是臨時需要的,所以不必為之命名。匿名類型的聲明語法如下:
var user = new { ID = Guid.NewGuid(), Name = "Apollo" };
四、擴展方法
擴展方法是微軟為擴展已有框架而創造的一個語法糖,.NET Framework 3.5就是通過很多擴展方法實現了對.NET Framework 2.0的升級擴展的。擴展方法很神奇,被擴展的對象可以不知道擴展方法的存在,就能在行為上得到擴展。擴展方法也很蹩腳,如果使用者不知情,很可能不知道對象具有擴展行為;抑或知道有擴展方法,但是不知道要引用哪個擴展庫以使其支持擴展的行為。擴展方法的語法如下:
1 public static class UserExt 2 { 3 public static void Drink(this User user, object water) 4 { 5 … 6 } 7 }
五、Lambda表達式
Lambda表達式是由委托以及匿名方法發展而來的,它可將表達式或代碼塊(匿名方法)賦給一個變量,從而以最少量的輸入實現編碼目的。Lambda表達式一般配合IEnumerable<T>的靜態擴展方法使用,完成對象集合的快捷查詢操作。Lambda表達式的語法如下:
var user = db.Users.FirstOrDefault(o => o.Account == "Apollo");
六、標准查詢操作符
System.Linq.Enumerable靜態類聲明了一套標准查詢操作符(Standard Query Operators,SQO)方法集合。標准查詢操作符的語法和標准SQL很相似,這不是偶然,而是微軟有意為之,以使熟悉SQL的程序員們更容易上手。標准查詢操作符的基本語法如下:
1 using (var db = new EntityContext()) 2 { 3 var roles = from o in db.Users 4 where o.Account == "Apollo" 5 select o.Roles; 6 … 7 }
標准查詢操作符和Lambda表達式的關系非常密切。編譯器會將上訴表達式轉化為下列以Lambda表達式為參數的顯式擴展方法調用序列:
1 using (var db = new EntityContext()) 2 { 3 var roles = db.Users.Where(o => o.Account == "Apollo").Select(o => o.Roles); 4 }
標准查詢操作符
接下來,將針對數據查詢操作中常用的條件查詢、投影、分區、排序、分組、集合、元素、量詞和聚集等標准查詢操作符進行分類介紹。
一、條件操作符
條件操作符Where類似於SQL中的WHERE子句,用於實現條件查詢。下列擴展方法表達式查詢滿足條件“角色不為空”的用戶集合:
var user = db.Users.Where(o => o.Roles != null);
對應的標准查詢操作符表達式為:
1 var users = from o in db.Users 2 where o.Roles != null 3 select o;
二、投影操作符
投影操作符Select類似於SQL中的SELECT子句,將對象投影為一個匿名類型實例,用於控制指定查詢迭代器顯示或者處理的對象屬性。另外,需要注意的是,擴展方法表達式中的Select操作符並非必須的,省略模式下,會返回完整的被投影對象。下列擴展方法表達式將用戶的帳號和密碼信息投影為一個匿名類型:
var users = db.Users.Select(o => new { o.Account, o.Password });
對應的標准查詢操作符表達式為:
1 var users = from o in db.Users 2 select new { o.Account, o.Password };
三、分區操作符
分區操作符實現對象的分區操作。其中,Take操作符類似於SQL中的TOP操作符,下列擴展方法表達式返回前5個用戶對象:
var users = db.Users.OrderBy(o => o.Roles.Count).Take(5);
Skip操作符用於跳過指定個數對象並返回序列中的剩余對象,下列擴展方法表達式返回除前10個用戶外的剩余用戶:
var users = db.Users.OrderBy(o => o.Roles.Count).Skip(10);
TakeWhile操作符用於返回條件表達式值為真時的相鄰元素集合,下列擴展方法表達式返回第一個擁有3個角色的用戶之前的所有用戶集合:
var users = db.Users.OrderBy(o => o.Roles.Count).TakeWhile(o => o.Roles.Count == 3);
SkipWhile操作符用於跳過條件表達式值為真時的元素,並返回剩下的元素集合,下列擴展方法表達式返回第一個擁有3個角色的用戶之后的所有用戶集合:
var users = db.Users.OrderBy(o => o.Roles.Count).SkipWhile(o => o.Roles == 3);
四、排序操作符
排序操作符實現對象的排序功能,包括OrderBy、OrderByDescending、ThenBy、ThenByDescending和Reverse五個操作符。其中OrderBy操作符實現對象的升序排列,相當於SQL中的ORDER BY ASC子句,下列擴展方法表達式實現用戶按擁有的角色數進行升序排列:
var users = db.Users.OrderBy(o => o.Roles.Count);
對應的標准查詢操作符表達式為:
1 var users = from o in db.Users 2 orderby o.Roles.Count 3 select o;
OrderByDescending操作符實現對象的降序排列,相當於SQL中的ORDER BY DESC子句,下列擴展方法表達式實現用戶按擁有的角色數進行降序排列:
var users = db.Users.OrderByDescending(o => o.Roles.Count);
對應的標准查詢操作符表達式為:
1 var users = from o in db.Users 2 orderby o.Roles.Count descending 3 select o;
ThenBy、ThenByDescending和Reverse操作符只能針對IOrderedEnumerable接口對象使用,所以一般緊跟在OrderBy/OrderByDesending操作符方法后使用。ThenBy操作符由編譯器翻譯為對OrderBy操作符的再次調用;ThenByDescending操作符由編譯器翻譯為對OrderByDescending操作符的再次調用;Reverse操作符實現對象的排序反向。這里不再一一舉例。
五、分組操作符
分組操作符GroupBy類似於SQL中的GROUP BY子句,實現對象的分組操作。下列擴展方法表達式實現用戶對象按擁有的角色數量進行分組:
var users = db.Users.GroupBy(o => o.Roles.Count);
對應的標准查詢操作符表達式為:
1 var users = from o in db.Users 2 group o by o.Roles.Count into g 3 select new { RoleCount = g.Key, Group = g };
六、集合操作符
集合操作符包括Distinct、Union、intersect和Except四個操作符,除Distinct外,其他三個操作符都可將兩個序列組合成一個序列。Distinct操作符類似於SQL中的DISTINCT關鍵字,用於刪除序列中具有重復值的對象。下列擴展方法表達式實現將用戶角色中的重復角色刪除功能:
var roles = user.Roles.Distinct();
Union操作符類似於SQL中的UNION關鍵字,用於求具有同樣結構的兩個序列的並集。下列擴展方法表達式實現將用戶1和用戶2所擁有的角色組合成一個角色集合,並排除其中重復的角色:
var roles = user1.Roles.Union(user2.Roles);
Intersect操作符類似於SQL中的INTERSECT關鍵字,用於求具有同樣結構的兩個序列的交集。下列擴展方法表達式返回用戶1和用戶2都具有的角色集合:
var roles = user1.Roles.Intersect(user2.Roles);
Except操作符類似於SQL中的EXCEPT關鍵字,用於返回第一個序列中有、但第二個序列中沒有的對象集合。下列擴展方法表達式返回用戶1擁有,而用戶2沒有的角色集合:
var roles = user1.Roles.Except(user2.Roles);
七、元素操作符
元素操作符包括兩組操作符,分別是用於從一個IEnumerable<T>序列中返回滿足條件的單個對象或無滿足條件對象時拋異常的First、Last和Single操作符,以及返回滿足條件的單個對象或無滿足條件對象時返回空對象的FirstOrDefault、LastOrDefault和SingleOrDefault操作符。其中First和FirstOrDefault操作符用於返回第一個滿足條件的對象。下列擴展方法表達式返回第一個擁有三個角色的用戶:
var user = db.Users.FirstOrDefault(o => o.Roles.Count == 3);
Last和LastOrDefault操作符用於返回最后一個滿足條件的對象。下列擴展方法表達式返回最后一個擁有三個角色的用戶:
var user = db.Users.LastOrDefault(o => o.Roles.Count == 3);
Single和SingleOrDefault操作符用於返回滿足條件的序列中的唯一元素,如果序列中包含不止一個元素,將會拋異常。下列擴展方法表達式返回帳號為“Apollo”的唯一用戶,如果有多個用戶帳號都為“Apollo”,則拋異常:
var user = db.Users.SingleOrDefault(o => o.Account == "Apollo");
八、量詞操作符
量詞操作符包括 Any、All和Contains三個操作符,用於檢查序列中是否有一些對象或所有對象滿足條件。其中,Any操作符用於檢查序列中是否有任何一個對象滿足條件。下列擴展方法表達式當有任何一個用戶擁有三個角色時返回真,否則返回假:
var result = db.Users.Any(o => o.Roles.Count == 3);
All操作符用於檢查序列中是否所有對象均滿足條件。下列擴展方法表達式當所有用戶均擁有三個角色時返回真,否則返回假:
var result = db.Users.All(o => o.Roles.Count == 3);
Contains操作符用於檢查序列中是否包含指定的對象。下列擴展方法表達式當集合中包含用戶1則返回真,否則返回假:
var result = db.Users.Where(o => o.Roles.Count == 3).Contains(user1);
九、聚集操作符
聚集操作符包括Count、Min、Max、Sum和Average等多個操作符,用於對對象集合進行統計計算。其中,Count操作符類似於SQL中的COUNT關鍵字,用於計算序列中滿足條件的對象個數。下列擴展方法表達式返回擁有3個角色的用戶數量:
var result = db.Users.Count(o => o.Roles.Count == 3);
Min操作符類似於SQL中的MIN關鍵字,用於返回按條件計算的最小值。下列擴展方法表達式返回擁有最少角色數量的用戶所擁有的角色數量:
var result = db.Users.Min(o => o.Roles.Count);
Max操作符類似於SQL中的MAX關鍵字,用於返回按條件計算的最大值。下列擴展方法表達式返回擁有角色數量最多的用戶所擁有的角色數量:
var result = db.Users.Max(o => o.Roles.Count);
Sum操作符類似於SQL中的SUM關鍵字,用於返回按條件計算的總數。下列擴展方法表達式返回已賦予用戶的所有角色總數:
var result = db.Users.Sum(o => o.Roles.Count);
Average操作符類似於SQL中的AVERAGE關鍵字,用於返回按條件計算的平均值。下列擴展方法表達式,用於返回用戶所擁有的角色平均數:
var result = db.Users.Average(o => o.Roles.Count);
總結
本文首先給出了LINQ技術的官方實現集合,以及LINQ to Entities實現的技術細節;然后概要介紹了與LINQ to Entities相關的幾個基本概念;最后對LINQ to Entities常用的標准查詢操作符的使用進行了分類介紹,從中也可以看出LINQ to Entities和SQL的功能基本是一一對應的。
下一篇文章將綜合運用本系列前文所介紹的所有技術,完成一個RBAC模型的設計與實現。