您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢”這篇文章吧。
一、前言
EF的CodeFirst是個(gè)好東西,讓我們完全不用考慮數(shù)據(jù)庫(kù)端(注意,這里并不是說(shuō)不需要對(duì)數(shù)據(jù)庫(kù)知識(shí)進(jìn)行了解),一切工作都可以通過(guò)代碼來(lái)完成。EF是ORM,已經(jīng)把數(shù)據(jù)訪問操作封裝得很好了,可以直接在業(yè)務(wù)層中使用,那我們?yōu)槭裁催€要對(duì)其進(jìn)行那么多封裝呢?在我看來(lái),封裝至少能帶來(lái)如下的好處:
把EF的相關(guān)對(duì)象封裝在數(shù)據(jù)訪問層中,解除了業(yè)務(wù)層對(duì)EF的依賴。
統(tǒng)一EF的數(shù)據(jù)操作,以保證業(yè)務(wù)層使用相同的代碼規(guī)范
隱藏EF的敏感配置,降低EF的使用難度
這里就引入一個(gè)問題,應(yīng)該怎樣來(lái)進(jìn)行EF的封裝呢,既要保證使用的統(tǒng)一與方便性,又要保持EF的靈便性,否則,封裝將變成給業(yè)務(wù)層設(shè)置障礙。下面,主要針對(duì)數(shù)據(jù)查詢進(jìn)對(duì)可能出現(xiàn)的誤用情況進(jìn)行分析。
二、查詢問題分析
(一) 數(shù)據(jù)查詢應(yīng)該在哪做
在EF中,面向?qū)ο蟮臄?shù)據(jù)查詢主要提供了兩種方式:
TEntity DbSet<TEntity>.Find(params object[] keyValues):針對(duì)主鍵設(shè)計(jì)的通過(guò)主鍵查找單個(gè)實(shí)體,會(huì)先在EF的本地?cái)?shù)據(jù)集Local中進(jìn)行查詢,如果沒有,再去數(shù)據(jù)庫(kù)中查詢。
IQueryable<T>、IEnumerable<T>類型的所有數(shù)據(jù)查詢的擴(kuò)展方法(由于DbSet<T>繼承于IQueryable<T>與IEnumerable<T>),如SingleOrDefault,F(xiàn)irstOrDefault,Where等。其中IQueryable<T>的擴(kuò)展方法會(huì)先收集需求,到最后一步再生成相應(yīng)的SQL語(yǔ)句進(jìn)行數(shù)據(jù)查詢;而IEnumerable<T>的擴(kuò)展方法則是在查詢的第一步就生成相應(yīng)的SQL語(yǔ)句獲取數(shù)據(jù)到內(nèi)存中,后面的操作都是以內(nèi)存中的數(shù)據(jù)為基礎(chǔ)進(jìn)行操作的。
以上兩種方式為EF的數(shù)據(jù)查詢提供了極大的自由度,這個(gè)自由度是我們?cè)诜庋b的時(shí)候需要保持的。但是,在閱讀不少人(其中不乏工作了幾年的)對(duì)EF的封裝,設(shè)計(jì)統(tǒng)一的數(shù)據(jù)操作接口Repository中關(guān)于數(shù)據(jù)查詢的操作中,通常會(huì)犯如下幾種失誤:
設(shè)計(jì)了很多GetByName,GetByXX,GetByXXX的操作,這些操作通常并不是所有實(shí)體都會(huì)用到,只是部分實(shí)體的部分業(yè)務(wù)用到,或者是“估計(jì)會(huì)用到”。
定義了按條件查詢的SingleOrDefault,F(xiàn)irstOrDefault,Count,GetByPredicate(predicate)等方法,但是對(duì)于條件predicate的類型是使用Expression<Func<TEntity, boo>>還是Func<TEntity, bool>很糾結(jié),最后干脆兩個(gè)都設(shè)計(jì),相當(dāng)于把IQueryable<T>,IEnumerable<T>的方法再過(guò)一遍。
定義了獲取全部數(shù)據(jù)的GetAll()方法,但卻使用了IEnumerable<TEntity>類型的返回值,明白的同學(xué)都知道,這相當(dāng)于把整個(gè)表的數(shù)據(jù)都加載到內(nèi)存中,問題很嚴(yán)重,設(shè)計(jì)者卻不知道。
諸如此類,各種奇葩的查詢操作層出不窮,這些操作或者破壞了EF數(shù)據(jù)查詢?cè)械撵`活性,或者畫蛇添足。
其實(shí),這么多失誤的原因只有一個(gè),設(shè)計(jì)者忘記了EF是ORM,把EF當(dāng)作ado.net來(lái)使用了。只要記著EF是ORM,以上這些功能已經(jīng)實(shí)現(xiàn)了,就不要去重復(fù)實(shí)現(xiàn)了。那么以上的問題就非常好解決了,只要:
在數(shù)據(jù)操作Repository接口中把EF的DbSet<TEntity>開放成一個(gè)只讀的IQueryable<TEntity>類型的屬性提供給業(yè)務(wù)層作為數(shù)據(jù)查詢的數(shù)據(jù)源
就可以了。這個(gè)數(shù)據(jù)源是只讀的,并且類型是IQueryable<T>,就保證了它只能作為數(shù)據(jù)查詢的數(shù)據(jù)源,而不像開放了DbSet<T>類型那樣可以在業(yè)務(wù)層中調(diào)用EF的內(nèi)部方法進(jìn)行增、刪、改等操作。另外IQueryable<T>類型保持了EF原有的查詢自由性與靈活性,簡(jiǎn)單明了。這個(gè)數(shù)據(jù)集還可以傳遞到業(yè)務(wù)層的各個(gè)層次,以實(shí)現(xiàn)在哪需要數(shù)據(jù)就在哪查的靈活性。
(二) 循環(huán)中的查詢陷阱
EF的導(dǎo)航屬性是延遲加載的,延遲加載的優(yōu)點(diǎn)就是不用到不加載,一次只加載必要的數(shù)據(jù),這減少了每次加載的數(shù)據(jù)量,但缺點(diǎn)也不言自明:極大的增加了數(shù)據(jù)庫(kù)連接的次數(shù),比如如下這么個(gè)簡(jiǎn)單的需求:
輸出每個(gè)用戶擁有的角色數(shù)量
根據(jù)這個(gè)需求,很容易就寫出了如下的代碼:
遍歷所有用戶信息,輸出每個(gè)用戶信息中角色(導(dǎo)航屬性)的數(shù)量。
上面這段代碼邏輯很清晰,看似沒有什么問題。我們來(lái)分析一下代碼的執(zhí)行過(guò)程:
132行,從IOC容器中獲取用戶倉(cāng)儲(chǔ)接口的實(shí)例,這沒什么問題。
133行,取出所有用戶信息(memberRepository.Entities),執(zhí)行SQL如下:
SELECT [Extent1].[Id] AS [Id], [Extent1].[UserName] AS [UserName], [Extent1].[Password] AS [Password], [Extent1].[NickName] AS [NickName], [Extent1].[Email] AS [Email], [Extent1].[IsDeleted] AS [IsDeleted], [Extent1].[AddDate] AS [AddDate], [Extent1].[Timestamp] AS [Timestamp], [Extent2].[Id] AS [Id1] FROM [dbo].[Members] AS [Extent1] LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id]
雖然EF生成的SQL有些復(fù)雜,但還是沒什么問題
3. 136行,就開始有問題了,每次循環(huán)都會(huì)連接一次數(shù)據(jù)庫(kù),執(zhí)行一次如下查詢(最后一個(gè)1是用戶編號(hào)):
exec sp_executesql N'SELECT [Extent2].[Id] AS [Id], [Extent2].[Name] AS [Name], [Extent2].[Description] AS [Description], [Extent2].[RoleTypeNum] AS [RoleTypeNum], [Extent2].[IsDeleted] AS [IsDeleted], [Extent2].[AddDate] AS [AddDate], [Extent2].[Timestamp] AS [Timestamp] FROM [dbo].[RoleMembers] AS [Extent1] INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[Role_Id] = [Extent2].[Id] WHERE [Extent1].[Member_Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
試想,如果有100個(gè)用戶,就要連接100次數(shù)據(jù)庫(kù),這么一個(gè)簡(jiǎn)單的需求,連接了101次數(shù)據(jù)庫(kù),還不得讓數(shù)據(jù)庫(kù)瘋掉了。
當(dāng)然,有同學(xué)可以要說(shuō),這里用了延遲加載才會(huì)多了很多連接數(shù)據(jù)庫(kù)的次數(shù),你可以立即加載啊,把Role角色一次性加載進(jìn)來(lái)。好吧,我們來(lái)看看立即加載:
143行,在取所有用戶信息的時(shí)候使用Include方法把與用戶關(guān)聯(lián)的所有角色信息也一并查詢出來(lái)了,這樣在循環(huán)遍歷的時(shí)候就不會(huì)再連接數(shù)據(jù)庫(kù)去查詢角色信息了。但是如果看到執(zhí)行的SQL語(yǔ)句,估計(jì)你想死的心情都有了。執(zhí)行的查詢?nèi)缦拢?/p>
SELECT [Project1].[Id] AS [Id], [Project1].[UserName] AS [UserName], [Project1].[Password] AS [Password], [Project1].[NickName] AS [NickName], [Project1].[Email] AS [Email], [Project1].[IsDeleted] AS [IsDeleted], [Project1].[AddDate] AS [AddDate], [Project1].[Timestamp] AS [Timestamp], [Project1].[Id1] AS [Id1], [Project1].[C1] AS [C1], [Project1].[Id2] AS [Id2], [Project1].[Name] AS [Name], [Project1].[Description] AS [Description], [Project1].[RoleTypeNum] AS [RoleTypeNum], [Project1].[IsDeleted1] AS [IsDeleted1], [Project1].[AddDate1] AS [AddDate1], [Project1].[Timestamp1] AS [Timestamp1] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[UserName] AS [UserName], [Extent1].[Password] AS [Password], [Extent1].[NickName] AS [NickName], [Extent1].[Email] AS [Email], [Extent1].[IsDeleted] AS [IsDeleted], [Extent1].[AddDate] AS [AddDate], [Extent1].[Timestamp] AS [Timestamp], [Extent2].[Id] AS [Id1], [Join2].[Id] AS [Id2], [Join2].[Name] AS [Name], [Join2].[Description] AS [Description], [Join2].[RoleTypeNum] AS [RoleTypeNum], [Join2].[IsDeleted] AS [IsDeleted1], [Join2].[AddDate] AS [AddDate1], [Join2].[Timestamp] AS [Timestamp1], CASE WHEN ([Join2].[Member_Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] FROM [dbo].[Members] AS [Extent1] LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id] LEFT OUTER JOIN (SELECT [Extent3].[Member_Id] AS [Member_Id], [Extent4].[Id] AS [Id], [Extent4].[Name] AS [Name], [Extent4].[Description] AS [Description], [Extent4].[RoleTypeNum] AS [RoleTypeNum], [Extent4].[IsDeleted] AS [IsDeleted], [Extent4].[AddDate] AS [AddDate], [Extent4].[Timestamp] AS [Timestamp] FROM [dbo].[RoleMembers] AS [Extent3] INNER JOIN [dbo].[Roles] AS [Extent4] ON [Extent4].[Id] = [Extent3].[Role_Id] ) AS [Join2] ON [Extent1].[Id] = [Join2].[Member_Id] ) AS [Project1] ORDER BY [Project1].[Id] ASC, [Project1].[Id1] ASC, [Project1].[C1] ASC
(三) 導(dǎo)航屬性的查詢陷阱
我們?cè)賮?lái)回顧一下導(dǎo)航屬性的長(zhǎng)相(以用戶信息中的角色信息為例):
可以看到,集合類的導(dǎo)航屬性是一個(gè)ICollection<T>類型的集合,其實(shí)現(xiàn)類可以是通常使用List<T>或者HashSet<T>。用了ICollection<T>,就限定了集合類的導(dǎo)航屬性是一個(gè)內(nèi)存集合,只要用到這個(gè)導(dǎo)航屬性,就必須把集合中的所有數(shù)據(jù)都加載到內(nèi)存中,才能進(jìn)行后續(xù)操作。比如上面的例子中,我們的需求只是想知道用戶擁有角色的數(shù)量,原意只是要執(zhí)行一下SQL的Count語(yǔ)句即可,卻想不到EF是把這個(gè)集合加載到內(nèi)存中(上面的語(yǔ)句,是把當(dāng)前用戶的所有角色信息查詢出來(lái)),再在內(nèi)存中進(jìn)行計(jì)數(shù),這無(wú)形中是一個(gè)很大的資源浪費(fèi)。比如在一個(gè)商城系統(tǒng)中,我們想了解一種商品的銷量(product.Orders.Count),那就可能把幾萬(wàn)條訂單信息都加載到內(nèi)存中,再進(jìn)行計(jì)數(shù),這將是災(zāi)難性的資源消耗。
讀到這里,是不是對(duì)EF非常失望?
三、查詢應(yīng)該怎么設(shè)計(jì)
上面的問題,在項(xiàng)目的開發(fā)階段,根本不是問題,因?yàn)檐浖諛幽芘艿闷饋?lái),而且跑得好好的。但是等網(wǎng)站上線的時(shí)候,用戶量上來(lái)的時(shí)候,這些性能殺手就暴露無(wú)遺了。是問題,總要想辦法解決的。
下面就來(lái)說(shuō)說(shuō)我的解決方案,至于方案靠譜不靠譜,讀者自行判斷。
(一) 查詢數(shù)據(jù)集設(shè)計(jì)
在前面的設(shè)計(jì)中,實(shí)體的數(shù)據(jù)倉(cāng)儲(chǔ)接口已經(jīng)向上層暴露了一個(gè)IQueryable<TEntity>的接口了,為什么暴露這個(gè)接口,上面也說(shuō)了很多了。下面,以賬戶模塊為例,我們就來(lái)看看怎樣把這個(gè)查詢數(shù)據(jù)集往上傳遞。
首先,不要忘了,我們的項(xiàng)目結(jié)構(gòu)是這樣的:
對(duì)注入的Repository接口進(jìn)行保護(hù)
在核心業(yè)務(wù)實(shí)現(xiàn)類(AccountService)中,我們進(jìn)行了各個(gè)相關(guān)實(shí)體的Repository接口的注入
這里要注意,實(shí)體的Repository接口只能在業(yè)務(wù)層中使用,以防止開發(fā)者在展現(xiàn)層中調(diào)用增、刪、改等數(shù)據(jù)操作以實(shí)現(xiàn)業(yè)務(wù),而不是在業(yè)務(wù)層中進(jìn)行業(yè)務(wù)實(shí)現(xiàn)。因而,注入的實(shí)體的Repository接口屬性可訪問性要修改為 protected。
開放查詢數(shù)據(jù)集供展現(xiàn)層使用
業(yè)務(wù)層中的Repository接口都設(shè)置為 protected 了,那么在展現(xiàn)層無(wú)法訪問 IEntityRepository.Entities 數(shù)據(jù)集了,怎樣實(shí)現(xiàn)展現(xiàn)層的數(shù)據(jù)的查詢呢,很簡(jiǎn)單,只要在業(yè)務(wù)接口中把 IEntityRepository.Entities 數(shù)據(jù)集 再包裝成一個(gè)IQueryable<T>的查詢數(shù)據(jù)集開發(fā)出去,就可以了。
在業(yè)務(wù)實(shí)現(xiàn)類中進(jìn)行 IEntityRepository.Entities 數(shù)據(jù)集的包裝:
經(jīng)過(guò)這樣的封裝,在業(yè)務(wù)層中,我們可以使用 IEntityRepository.Entities 數(shù)據(jù)集 進(jìn)行數(shù)據(jù)查詢,在展現(xiàn)層中使用業(yè)務(wù)契約中開放的數(shù)據(jù)集進(jìn)行查詢。由于開發(fā)的數(shù)據(jù)集仍是IQueryable<T>類型,對(duì)EF的查詢自由度沒有損耗。
(二) 查詢陷阱的應(yīng)對(duì)方案
對(duì)于前面提到的EF的查詢陷阱,我提出的解決方案就是
通過(guò)IQueryable<T>的 Select(selector) 擴(kuò)展方法來(lái)按需查詢。
首先分析好當(dāng)前業(yè)務(wù)中需要什么數(shù)據(jù),要什么取什么,最后的數(shù)據(jù)用匿名對(duì)象裝載。
比如前面提到的 輸出用戶擁有的角色數(shù)量 這個(gè)需求,實(shí)現(xiàn)方案如下:
以上代碼執(zhí)行的查詢語(yǔ)句如下:
SELECT [Extent1].[Id] AS [Id], (SELECT COUNT(1) AS [A1] FROM [dbo].[RoleMembers] AS [Extent2] WHERE [Extent1].[Id] = [Extent2].[Member_Id]) AS [C1] FROM [dbo].[Members] AS [Extent1]
相當(dāng)簡(jiǎn)潔,這才是我們需要的效果。
(三) 匿名對(duì)象方案與實(shí)體對(duì)象方案對(duì)比
匿名對(duì)象的方案雖然達(dá)到了我們想要的效果,但對(duì)比實(shí)體對(duì)象方案,又有什么不同呢,下面我們來(lái)對(duì)比一下:
數(shù)據(jù)傳遞性、復(fù)用性:
-匿名對(duì)象:基本上屬于一次性數(shù)據(jù),無(wú)法整體傳遞,無(wú)法復(fù)用。
+實(shí)體對(duì)象:傳遞性,復(fù)用性良好。
對(duì)重構(gòu)、方法提取的支持:
-匿名對(duì)象:由于數(shù)據(jù)無(wú)法傳遞,寫出的代碼很難進(jìn)行重構(gòu),我就普寫過(guò)幾百行代碼而無(wú)法提取子方法重構(gòu)的方法。
+實(shí)體對(duì)象:數(shù)據(jù)對(duì)代碼重構(gòu)、方法提取支持良好。
對(duì)緩存命中率的影響:
-匿名對(duì)象:數(shù)據(jù)與具體的業(yè)務(wù)場(chǎng)景(參數(shù)、條件等)密切關(guān)聯(lián),緩存命中率可能會(huì)較低。
+實(shí)體對(duì)象:數(shù)據(jù)易復(fù)用,緩存命中率可能會(huì)較高。
不同層次的數(shù)據(jù)模型自動(dòng)映射轉(zhuǎn)換(AutoMapper等)
-匿名對(duì)象:屬性不定,類型不定,難以轉(zhuǎn)換。
+實(shí)體對(duì)象:輕松實(shí)現(xiàn)映射轉(zhuǎn)換。
數(shù)據(jù)利用率:
+匿名對(duì)象:數(shù)據(jù)按需獲取,利用率高,基本無(wú)浪費(fèi)。
-實(shí)體對(duì)象:數(shù)據(jù)都是整體取出,利用率低,浪費(fèi)大。
程序性能影響:
+匿名對(duì)象:容易寫出運(yùn)行高效的代碼,性能良好。
-實(shí)體對(duì)象:容易寫出性能低下的代碼。
通過(guò)上面的對(duì)比,希望能對(duì)方案的選擇提供一些參考,至于如何取舍,最終選擇什么方案,只能自己根據(jù)業(yè)務(wù)的特點(diǎn)來(lái)權(quán)衡了,合適用哪個(gè)就用哪個(gè)。
四、需求實(shí)現(xiàn)
前面已經(jīng)說(shuō)過(guò)不少次了,這里在明確的提一次,在這個(gè)架構(gòu)設(shè)計(jì)中,如果現(xiàn)有查詢方法不能滿足業(yè)務(wù)需求,需要添加一個(gè)相應(yīng)的查詢功能,你不需要到數(shù)據(jù)層去進(jìn)行操作,你只需要:
擴(kuò)展IQueryable<T>,給IQueryable<T>添加一個(gè)擴(kuò)展方法。
(一) 按屬性名稱排序
查詢離不開分頁(yè)查詢,分頁(yè)查詢之前通常會(huì)先排序,再查出指定頁(yè)的單頁(yè)數(shù)據(jù),先來(lái)說(shuō)說(shuō)按屬性排序的問題吧。
排序可以使用IQueryable<T>的OrderBy、OrderByDescending兩個(gè)擴(kuò)展方法來(lái)進(jìn)行,例如:
source.OrderBy(m => m.AddDate).ThenByDescending(m => m.IsDeleted);
這是系統(tǒng)提供的排序方法,但只支持 Expression<Func<TSource, TKey>> keySelector 類型的參數(shù),而我們?cè)邳c(diǎn)擊表格的表頭的時(shí)候,通常獲取到的是實(shí)體的屬性名稱的字符串,所以我們還需要擴(kuò)展一個(gè)支持屬性名稱的排序方法。
首先,定義一個(gè)類來(lái)封裝排序條件,排序條件通常包括屬性名稱與排序方向:
namespace GMF.Component.Tools { /// <summary> /// 屬性排序條件信息類 /// </summary> public class PropertySortCondition { /// <summary> /// 構(gòu)造一個(gè)指定屬性名稱的升序排序的排序條件 /// </summary> /// <param name="propertyName">排序?qū)傩悦Q</param> public PropertySortCondition(string propertyName) : this(propertyName, ListSortDirection.Ascending) { } /// <summary> /// 構(gòu)造一個(gè)排序?qū)傩悦Q和排序方式的排序條件 /// </summary> /// <param name="propertyName">排序?qū)傩悦Q</param> /// <param name="listSortDirection">排序方式</param> public PropertySortCondition(string propertyName, ListSortDirection listSortDirection) { PropertyName = propertyName; ListSortDirection = listSortDirection; } /// <summary> /// 獲取或設(shè)置 排序?qū)傩悦Q /// </summary> public string PropertyName { get; set; } /// <summary> /// 獲取或設(shè)置 排序方向 /// </summary> public ListSortDirection ListSortDirection { get; set; } } }
其次,我們接收的是排序條件是屬性名稱的字符串,實(shí)際還是要調(diào)用系統(tǒng)提供的Expression<Func<TSource, TKey>> keySelector類型參數(shù)的排序方法進(jìn)行排序。所以我們還需要一個(gè)把字符串條件轉(zhuǎn)換為排序表達(dá)式,并調(diào)用系統(tǒng)的排序方法。
private static class QueryableHelper<T> { // ReSharper disable StaticFieldInGenericType private static readonly ConcurrentDictionary<string, LambdaExpression> Cache = new ConcurrentDictionary<string, LambdaExpression>(); internal static IOrderedQueryable<T> OrderBy(IQueryable<T> source, string propertyName, ListSortDirection sortDirection) { dynamic keySelector = GetLambdaExpression(propertyName); return sortDirection == ListSortDirection.Ascending ? Queryable.OrderBy(source, keySelector) : Queryable.OrderByDescending(source, keySelector); } internal static IOrderedQueryable<T> ThenBy(IOrderedQueryable<T> source, string propertyName, ListSortDirection sortDirection) { dynamic keySelector = GetLambdaExpression(propertyName); return sortDirection == ListSortDirection.Ascending ? Queryable.ThenBy(source, keySelector) : Queryable.ThenByDescending(source, keySelector); } private static LambdaExpression GetLambdaExpression(string propertyName) { if (Cache.ContainsKey(propertyName)) { return Cache[propertyName]; } ParameterExpression param = Expression.Parameter(typeof (T)); MemberExpression body = Expression.Property(param, propertyName); LambdaExpression keySelector = Expression.Lambda(body, param); Cache[propertyName] = keySelector; return keySelector; } }
到此,有了前面的準(zhǔn)備,屬性名稱的排序就非常好寫了。為了使用方便,應(yīng)該做成IQueryable<T>的擴(kuò)展方法:
/// <summary> /// 把IQueryable[T]集合按指定屬性與排序方式進(jìn)行排序 /// </summary> /// <param name="source">要排序的數(shù)據(jù)集</param> /// <param name="propertyName">排序?qū)傩悦?lt;/param> /// <param name="sortDirection">排序方向</param> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <returns>排序后的數(shù)據(jù)集</returns> public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, ListSortDirection sortDirection = ListSortDirection.Ascending) { PublicHelper.CheckArgument(propertyName, "propertyName"); return QueryableHelper<T>.OrderBy(source, propertyName, sortDirection); } /// <summary> /// 把IQueryable[T]集合按指定屬性排序條件進(jìn)行排序 /// </summary> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <param name="source">要排序的數(shù)據(jù)集</param> /// <param name="sortCondition">列表屬性排序條件</param> /// <returns></returns> public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, PropertySortCondition sortCondition) { PublicHelper.CheckArgument(sortCondition, "sortCondition"); return source.OrderBy(sortCondition.PropertyName, sortCondition.ListSortDirection); } /// <summary> /// 把IOrderedQueryable[T]集合繼續(xù)按指定屬性排序方式進(jìn)行排序 /// </summary> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <param name="source">要排序的數(shù)據(jù)集</param> /// <param name="propertyName">排序?qū)傩悦?lt;/param> /// <param name="sortDirection">排序方向</param> /// <returns></returns> public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string propertyName, ListSortDirection sortDirection = ListSortDirection.Ascending) { PublicHelper.CheckArgument(propertyName, "propertyName"); return QueryableHelper<T>.ThenBy(source, propertyName, sortDirection); } /// <summary> /// 把IOrderedQueryable[T]集合繼續(xù)指定屬性排序方式進(jìn)行排序 /// </summary> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <param name="source">要排序的數(shù)據(jù)集</param> /// <param name="sortCondition">列表屬性排序條件</param> /// <returns></returns> public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, PropertySortCondition sortCondition) { PublicHelper.CheckArgument(sortCondition, "sortCondition"); return source.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection); }
這里使用了ListSortDirection來(lái)表示排序方向,當(dāng)然,你也可以定義ThenByDescending擴(kuò)展方法來(lái)進(jìn)行反序排序。上面的排序可以寫成如下所示:
source.OrderBy("AddDate").ThenBy("IsDeleted", ListSortDirection.Descending);
(二) 分頁(yè)查詢
下面來(lái)說(shuō)說(shuō)分頁(yè)查詢,通常分頁(yè)查詢的設(shè)計(jì)方法是在倉(cāng)儲(chǔ)操作Repository中定義特定的方法來(lái)獲取分頁(yè)的數(shù)據(jù),現(xiàn)在我們面對(duì)的是IQueryable<T>數(shù)據(jù)集,就不用那么麻煩了。只要定義一個(gè)專用于分頁(yè)查詢的擴(kuò)展方法即可。代碼如下:
/// <summary> /// 把IOrderedQueryable[T]集合繼續(xù)指定屬性排序方式進(jìn)行排序 /// </summary> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <param name="source">要排序的數(shù)據(jù)集</param> /// <param name="sortCondition">列表屬性排序條件</param> /// <returns></returns> public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, PropertySortCondition sortCondition) { PublicHelper.CheckArgument(sortCondition, "sortCondition"); return source.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection); } /// <summary> /// 從指定 IQueryable[T]集合 中查詢指定分頁(yè)條件的子數(shù)據(jù)集 /// </summary> /// <typeparam name="T">動(dòng)態(tài)類型</typeparam> /// <param name="source">要查詢的數(shù)據(jù)集</param> /// <param name="predicate">查詢條件謂語(yǔ)表達(dá)式</param> /// <param name="pageIndex">分頁(yè)索引</param> /// <param name="pageSize">分頁(yè)大小</param> /// <param name="total">輸出符合條件的總記錄數(shù)</param> /// <param name="sortConditions">排序條件集合</param> /// <returns></returns> public static IQueryable<T> Where<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, out int total, PropertySortCondition[] sortConditions = null) where T : Entity { PublicHelper.CheckArgument(source, "source"); PublicHelper.CheckArgument(predicate, "predicate"); PublicHelper.CheckArgument(pageIndex, "pageIndex"); PublicHelper.CheckArgument(pageSize, "pageSize"); total = source.Count(predicate); if (sortConditions == null || sortConditions.Length == 0) { source = source.OrderBy(m => m.AddDate); } else { int count = 0; IOrderedQueryable<T> orderSource = null; foreach (PropertySortCondition sortCondition in sortConditions) { orderSource = count == 0 ? source.OrderBy(sortCondition.PropertyName, sortCondition.ListSortDirection) : orderSource.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection); count++; } source = orderSource; } return source != null ? source.Where(predicate).Skip((pageIndex - 1) * pageSize).Take(pageSize) : Enumerable.Empty<T>().AsQueryable(); }
這樣,要獲取某頁(yè)數(shù)據(jù),只要調(diào)用這個(gè)擴(kuò)展方法即可,跟調(diào)用系統(tǒng)的擴(kuò)展方法一樣方便(其中total是總記錄數(shù))。
int total; var pageData = source.Where(m => m.IsDeleted, 4, 20, out total);
(三) 查詢實(shí)戰(zhàn)
下面,我們來(lái)實(shí)戰(zhàn)一下數(shù)據(jù)查詢。
首先,我們要查詢的數(shù)據(jù)將用下面這個(gè)類來(lái)顯示,其中LoginLogCount為當(dāng)前用戶的登錄次數(shù),RoleNames為用戶擁有的角色名稱集合,這兩個(gè)數(shù)據(jù)都來(lái)源于與Member有關(guān)聯(lián)的其他表。
namespace GMF.Demo.Site.Models { public class MemberView { public int Id { get; set; } public string UserName { get; set; } public string NickName { get; set; } public string Email { get; set; } public bool IsDeleted { get; set; } public DateTime AddDate { get; set; } public int LoginLogCount { get; set; } public IEnumerable<string> RoleNames { get; set; } } }
為了簡(jiǎn)化演示操作,引入分頁(yè)控件MVCPager來(lái)處理頁(yè)面上的分頁(yè)條的處理。
Controller中代碼如下,注意數(shù)據(jù)獲取的查詢代碼:
namespace GMF.Demo.Site.Web.Controllers { [Export] public class HomeController : Controller { [Import] public IAccountSiteContract AccountContract { get; set; } public ActionResult Index(int? id) { int pageIndex = id ?? 1; const int pageSize = 20; PropertySortCondition[] sortConditions = new[] { new PropertySortCondition("Id") }; int total; var memberViews = AccountContract.Members.Where(m => true, pageIndex, pageSize, out total, sortConditions).Select(m => new MemberView { UserName = m.UserName, NickName = m.NickName, Email = m.Email, IsDeleted = m.IsDeleted, AddDate = m.AddDate, LoginLogCount = m.LoginLogs.Count, RoleNames = m.Roles.Select(n => n.Name) }); PagedList<MemberView> model = new PagedList<MemberView>(memberViews, pageIndex, pageSize, total); return View(model); } } }
這里雖然使用了MVCPager,但并沒有使用她的分頁(yè)功能。分頁(yè)處理還是我們自己做的,只是使用了她的單頁(yè)數(shù)據(jù)模型類PageList<T>作為視圖模型
View代碼如下:
@using Webdiyer.WebControls.Mvc; @using GMF.Component.Tools; @model PagedList<GMF.Demo.Site.Models.MemberView> @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h3>Index</h3> @if (!User.Identity.IsAuthenticated) { @Html.ActionLink("登錄", "Login", "Account") } else { <div> 用戶 @User.Identity.Name 已登錄 @Html.ActionLink("退出", "Logout", "Account") </div> } <table> <tr> <th>UserName</th> <th>NickName</th> <th>Email</th> <th>IsDeleted</th> <th>AddDate</th> <th>LoginLogCount</th> <th>RoleNames</th> </tr> @foreach (var item in Model) { <tr> <td>@Html.DisplayFor(modelItem => item.UserName)</td> <td>@Html.DisplayFor(modelItem => item.NickName)</td> <td>@Html.DisplayFor(modelItem => item.Email)</td> <td>@Html.DisplayFor(modelItem => item.IsDeleted)</td> <td>@Html.DisplayFor(modelItem => item.AddDate)</td> <td style="text-align:center;"> @Html.DisplayFor(modelItem => item.LoginLogCount) </td> <td>@item.RoleNames.ExpandAndToString(",")</td> </tr> } </table> @Html.Pager(Model, new PagerOptions { PageIndexParameterName = "id" })
顯示效果如下:
查詢執(zhí)行的SQL語(yǔ)句如下:
SELECT [Project2].[Id] AS [Id], [Project2].[UserName] AS [UserName], [Project2].[NickName] AS [NickName], [Project2].[Email] AS [Email], [Project2].[IsDeleted] AS [IsDeleted], [Project2].[AddDate] AS [AddDate], [Project2].[C2] AS [C1], [Project2].[C1] AS [C2], [Project2].[Name] AS [Name] FROM ( SELECT [Limit1].[Id] AS [Id], [Limit1].[UserName] AS [UserName], [Limit1].[NickName] AS [NickName], [Limit1].[Email] AS [Email], [Limit1].[IsDeleted] AS [IsDeleted], [Limit1].[AddDate] AS [AddDate], [Join1].[Name] AS [Name], CASE WHEN ([Join1].[Member_Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], [Limit1].[C1] AS [C2] FROM (SELECT TOP (20) [Project1].[Id] AS [Id], [Project1].[UserName] AS [UserName], [Project1].[NickName] AS [NickName], [Project1].[Email] AS [Email], [Project1].[IsDeleted] AS [IsDeleted], [Project1].[AddDate] AS [AddDate], [Project1].[C1] AS [C1] FROM ( SELECT [Project1].[Id] AS [Id], [Project1].[UserName] AS [UserName], [Project1].[NickName] AS [NickName], [Project1].[Email] AS [Email], [Project1].[IsDeleted] AS [IsDeleted], [Project1].[AddDate] AS [AddDate], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[Id] ASC) AS [row_number] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[UserName] AS [UserName], [Extent1].[NickName] AS [NickName], [Extent1].[Email] AS [Email], [Extent1].[IsDeleted] AS [IsDeleted], [Extent1].[AddDate] AS [AddDate], (SELECT COUNT(1) AS [A1] FROM [dbo].[LoginLogs] AS [Extent2] WHERE [Extent1].[Id] = [Extent2].[Member_Id]) AS [C1] FROM [dbo].[Members] AS [Extent1] ) AS [Project1] ) AS [Project1] WHERE [Project1].[row_number] > 0 ORDER BY [Project1].[Id] ASC ) AS [Limit1] LEFT OUTER JOIN (SELECT [Extent3].[Member_Id] AS [Member_Id], [Extent4].[Name] AS [Name] FROM [dbo].[RoleMembers] AS [Extent3] INNER JOIN [dbo].[Roles] AS [Extent4] ON [Extent4].[Id] = [Extent3].[Role_Id] ) AS [Join1] ON [Limit1].[Id] = [Join1].[Member_Id] ) AS [Project2] ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC
執(zhí)行的SQL語(yǔ)句雖然比較復(fù)雜,但是確實(shí)是按我們的需求來(lái)進(jìn)行最簡(jiǎn)查詢的,比如我們沒有查詢Member的Password屬性,上面就沒有Password相關(guān)的語(yǔ)句,LoginLog的計(jì)數(shù),Roles的Name屬性的篩選,也沒有涉及該類的其他屬性的查詢。
以上是“EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。