溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

發(fā)布時(shí)間:2021-12-30 11:24:07 來(lái)源:億速云 閱讀:188 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“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)如下的好處:

  1. 把EF的相關(guān)對(duì)象封裝在數(shù)據(jù)訪問層中,解除了業(yè)務(wù)層對(duì)EF的依賴。

  2. 統(tǒng)一EF的數(shù)據(jù)操作,以保證業(yè)務(wù)層使用相同的代碼規(guī)范

  3. 隱藏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ù)查詢主要提供了兩種方式:

  1. 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ù)中查詢。

  2. 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ì)犯如下幾種失誤:

  1. 設(shè)計(jì)了很多GetByName,GetByXX,GetByXXX的操作,這些操作通常并不是所有實(shí)體都會(huì)用到,只是部分實(shí)體的部分業(yè)務(wù)用到,或者是“估計(jì)會(huì)用到”。

  2. 定義了按條件查詢的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ò)一遍

  3. 定義了獲取全部數(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è)需求,很容易就寫出了如下的代碼:

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

遍歷所有用戶信息,輸出每個(gè)用戶信息中角色(導(dǎo)航屬性)的數(shù)量。

上面這段代碼邏輯很清晰,看似沒有什么問題。我們來(lái)分析一下代碼的執(zhí)行過(guò)程:

  1. 132行,從IOC容器中獲取用戶倉(cāng)儲(chǔ)接口的實(shí)例,這沒什么問題。

  2. 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)看看立即加載:

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

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)相(以用戶信息中的角色信息為例):

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

可以看到,集合類的導(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)是這樣的:

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

  1. 對(duì)注入的Repository接口進(jìn)行保護(hù)

    在核心業(yè)務(wù)實(shí)現(xiàn)類(AccountService)中,我們進(jìn)行了各個(gè)相關(guān)實(shí)體的Repository接口的注入
    EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢
    這里要注意,實(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

  2. 開放查詢數(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ā)出去,就可以了。
    EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

  3. 在業(yè)務(wù)實(shí)現(xiàn)類中進(jìn)行 IEntityRepository.Entities 數(shù)據(jù)集的包裝:

    EF Code First如何實(shí)現(xiàn)數(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)方案如下:

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

以上代碼執(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ì)比一下:

  1. 數(shù)據(jù)傳遞性、復(fù)用性:
    -匿名對(duì)象:基本上屬于一次性數(shù)據(jù),無(wú)法整體傳遞,無(wú)法復(fù)用。
    +實(shí)體對(duì)象:傳遞性,復(fù)用性良好。

  2. 對(duì)重構(gòu)、方法提取的支持:
    -匿名對(duì)象:由于數(shù)據(jù)無(wú)法傳遞,寫出的代碼很難進(jìn)行重構(gòu),我就普寫過(guò)幾百行代碼而無(wú)法提取子方法重構(gòu)的方法。
    +實(shí)體對(duì)象:數(shù)據(jù)對(duì)代碼重構(gòu)、方法提取支持良好。

  3. 對(duì)緩存命中率的影響:
    -匿名對(duì)象:數(shù)據(jù)與具體的業(yè)務(wù)場(chǎng)景(參數(shù)、條件等)密切關(guān)聯(lián),緩存命中率可能會(huì)較低。
    +實(shí)體對(duì)象:數(shù)據(jù)易復(fù)用,緩存命中率可能會(huì)較高。

  4. 不同層次的數(shù)據(jù)模型自動(dòng)映射轉(zhuǎn)換(AutoMapper等)
    -匿名對(duì)象:屬性不定,類型不定,難以轉(zhuǎn)換。
    +實(shí)體對(duì)象:輕松實(shí)現(xiàn)映射轉(zhuǎn)換。

  5. 數(shù)據(jù)利用率:
    +匿名對(duì)象:數(shù)據(jù)按需獲取,利用率高,基本無(wú)浪費(fèi)。
    -實(shí)體對(duì)象:數(shù)據(jù)都是整體取出,利用率低,浪費(fèi)大。

  6. 程序性能影響:
    +匿名對(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" })

顯示效果如下:

EF Code First如何實(shí)現(xiàn)數(shù)據(jù)查詢

查詢執(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è)資訊頻道!

向AI問一下細(xì)節(jié)

免責(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)容。

AI