<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      Fork me on GitHub

      MVC實用架構設計(三)——EF-Code First(4):數據查詢

      前言

        首先對大家表示抱歉,這個系列已經將近一個月沒有更新了,相信大家等本篇更新都等得快失望了。實在沒辦法,由于本人水平有限,寫篇博客基本上要大半天的時間,最近實在是抽不出這么長段的空閑時間來寫。另外也是一直沒想好本篇應該怎樣寫比較容易理解,于是就一天一天的拖著了。廢話不多說,言歸正傳。

        EF的CodeFirst是個好東西,讓我們完全不用考慮數據庫端(注意,這里并不是說不需要對數據庫知識進行了解),一切工作都可以通過代碼來完成。EF是ORM,已經把數據訪問操作封裝得很好了,可以直接在業務層中使用,那我們為什么還要對其進行那么多封裝呢?在我看來,封裝至少能帶來如下的好處:

      1. 把EF的相關對象封裝在數據訪問層中,解除了業務層對EF的依賴。
      2. 統一EF的數據操作,以保證業務層使用相同的代碼規范
      3. 隱藏EF的敏感配置,降低EF的使用難度

        這里就引入一個問題,應該怎樣來進行EF的封裝呢,既要保證使用的統一與方便性,又要保持EF的靈便性,否則,封裝將變成給業務層設置障礙。下面,主要針對數據查詢進對可能出現的誤用情況進行分析。

      查詢問題分析

      數據查詢應該在哪做

        在EF中,面向對象的數據查詢主要提供了兩種方式:

      1. TEntity DbSet<TEntity>.Find(params object[] keyValues):針對主鍵設計的通過主鍵查找單個實體,會先在EF的本地數據集Local中進行查詢,如果沒有,再去數據庫中查詢。
      2. IQueryable<T>、IEnumerable<T>類型的所有數據查詢的擴展方法(由于DbSet<T>繼承于IQueryable<T>與IEnumerable<T>),如SingleOrDefault,FirstOrDefault,Where等。其中IQueryable<T>的擴展方法會先收集需求,到最后一步再生成相應的SQL語句進行數據查詢;而IEnumerable<T>的擴展方法則是在查詢的第一步就生成相應的SQL語句獲取數據到內存中,后面的操作都是以內存中的數據為基礎進行操作的。

        以上兩種方式為EF的數據查詢提供了極大的自由度,這個自由度是我們在封裝的時候需要保持的。但是,在閱讀不少人(其中不乏工作了幾年的)對EF的封裝,設計統一的數據操作接口Repository中關于數據查詢的操作中,通常會犯如下幾種失誤:

      1. 設計了很多GetByName,GetByXX,GetByXXX的操作,這些操作通常并不是所有實體都會用到,只是部分實體的部分業務用到,或者是“估計會用到”。
      2. 定義了按條件查詢的SingleOrDefault,FirstOrDefault,Count,GetByPredicate(predicate)等方法,但是對于條件predicate的類型是使用Expression<Func<TEntity, boo>>還是Func<TEntity, bool>很糾結,最后干脆兩個都設計,相當于把IQueryable<T>,IEnumerable<T>的方法再過一遍
      3. 定義了獲取全部數據的GetAll()方法,但卻使用了IEnumerable<TEntity>類型的返回值,明白的同學都知道,這相當于把整個表的數據都加載到內存中,問題很嚴重,設計者卻不知道。

        諸如此類,各種奇葩的查詢操作層出不窮,這些操作或者破壞了EF數據查詢原有的靈活性,或者畫蛇添足

        其實,這么多失誤的原因只有一個,設計者忘記了EF是ORM,把EF當作ado.net來使用了。只要記著EF是ORM,以上這些功能已經實現了,就不要去重復實現了。那么以上的問題就非常好解決了,只要:

      在數據操作Repository接口中把EF的DbSet<TEntity>開放成一個只讀的IQueryable<TEntity>類型的屬性提供給業務層作為數據查詢的數據源

      就可以了。這個數據源是只讀的,并且類型是IQueryable<T>,就保證了它只能作為數據查詢的數據源,而不像開放了DbSet<T>類型那樣可以在業務層中調用EF的內部方法進行增、刪、改等操作。另外IQueryable<T>類型保持了EF原有的查詢自由性與靈活性,簡單明了。這個數據集還可以傳遞到業務層的各個層次,以實現在哪需要數據就在哪查的靈活性。

      循環中的查詢陷阱

        EF的導航屬性是延遲加載的,延遲加載的優點就是不用到不加載,一次只加載必要的數據,這減少了每次加載的數據量,但缺點也不言自明:極大的增加了數據庫連接的次數,比如如下這么個簡單的需求:

      輸出每個用戶擁有的角色數量

        根據這個需求,很容易就寫出了如下的代碼:

        

        遍歷所有用戶信息,輸出每個用戶信息中角色(導航屬性)的數量。

        上面這段代碼邏輯很清晰,看似沒有什么問題。我們來分析一下代碼的執行過程:

      1. 132行,從IOC容器中獲取用戶倉儲接口的實例,這沒什么問題。
      2. 133行,取出所有用戶信息(memberRepository.Entities),執行SQL如下:
         1 SELECT 
         2 [Extent1].[Id] AS [Id], 
         3 [Extent1].[UserName] AS [UserName], 
         4 [Extent1].[Password] AS [Password], 
         5 [Extent1].[NickName] AS [NickName], 
         6 [Extent1].[Email] AS [Email], 
         7 [Extent1].[IsDeleted] AS [IsDeleted], 
         8 [Extent1].[AddDate] AS [AddDate], 
         9 [Extent1].[Timestamp] AS [Timestamp], 
        10 [Extent2].[Id] AS [Id1]
        11 FROM  [dbo].[Members] AS [Extent1]
        12 LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id]

         雖然EF生成的SQL有些復雜,但還是沒什么問題

      3. 136行,就開始有問題了,每次循環都會連接一次數據庫,執行一次如下查詢(最后一個1是用戶編號):
         1 exec sp_executesql N'SELECT 
         2 [Extent2].[Id] AS [Id], 
         3 [Extent2].[Name] AS [Name], 
         4 [Extent2].[Description] AS [Description], 
         5 [Extent2].[RoleTypeNum] AS [RoleTypeNum], 
         6 [Extent2].[IsDeleted] AS [IsDeleted], 
         7 [Extent2].[AddDate] AS [AddDate], 
         8 [Extent2].[Timestamp] AS [Timestamp]
         9 FROM  [dbo].[RoleMembers] AS [Extent1]
        10 INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[Role_Id] = [Extent2].[Id]
        11 WHERE [Extent1].[Member_Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

         試想,如果有100個用戶,就要連接100次數據庫,這么一個簡單的需求,連接了101次數據庫,還不得讓數據庫瘋掉了。

        當然,有同學可以要說,這里用了延遲加載才會多了很多連接數據庫的次數,你可以立即加載啊,把Role角色一次性加載進來。好吧,我們來看看立即加載:

        143行,在取所有用戶信息的時候使用Include方法把與用戶關聯的所有角色信息也一并查詢出來了,這樣在循環遍歷的時候就不會再連接數據庫去查詢角色信息了。但是如果看到執行的SQL語句,估計你想死的心情都有了。執行的查詢如下:

       1 SELECT 
       2 [Project1].[Id] AS [Id], 
       3 [Project1].[UserName] AS [UserName], 
       4 [Project1].[Password] AS [Password], 
       5 [Project1].[NickName] AS [NickName], 
       6 [Project1].[Email] AS [Email], 
       7 [Project1].[IsDeleted] AS [IsDeleted], 
       8 [Project1].[AddDate] AS [AddDate], 
       9 [Project1].[Timestamp] AS [Timestamp], 
      10 [Project1].[Id1] AS [Id1], 
      11 [Project1].[C1] AS [C1], 
      12 [Project1].[Id2] AS [Id2], 
      13 [Project1].[Name] AS [Name], 
      14 [Project1].[Description] AS [Description], 
      15 [Project1].[RoleTypeNum] AS [RoleTypeNum], 
      16 [Project1].[IsDeleted1] AS [IsDeleted1], 
      17 [Project1].[AddDate1] AS [AddDate1], 
      18 [Project1].[Timestamp1] AS [Timestamp1]
      19 FROM ( SELECT 
      20     [Extent1].[Id] AS [Id], 
      21     [Extent1].[UserName] AS [UserName], 
      22     [Extent1].[Password] AS [Password], 
      23     [Extent1].[NickName] AS [NickName], 
      24     [Extent1].[Email] AS [Email], 
      25     [Extent1].[IsDeleted] AS [IsDeleted], 
      26     [Extent1].[AddDate] AS [AddDate], 
      27     [Extent1].[Timestamp] AS [Timestamp], 
      28     [Extent2].[Id] AS [Id1], 
      29     [Join2].[Id] AS [Id2], 
      30     [Join2].[Name] AS [Name], 
      31     [Join2].[Description] AS [Description], 
      32     [Join2].[RoleTypeNum] AS [RoleTypeNum], 
      33     [Join2].[IsDeleted] AS [IsDeleted1], 
      34     [Join2].[AddDate] AS [AddDate1], 
      35     [Join2].[Timestamp] AS [Timestamp1], 
      36     CASE WHEN ([Join2].[Member_Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
      37     FROM   [dbo].[Members] AS [Extent1]
      38     LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id]
      39     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]
      40         FROM  [dbo].[RoleMembers] AS [Extent3]
      41         INNER JOIN [dbo].[Roles] AS [Extent4] ON [Extent4].[Id] = [Extent3].[Role_Id] ) AS [Join2] ON [Extent1].[Id] = [Join2].[Member_Id]
      42 )  AS [Project1]
      43 ORDER BY [Project1].[Id] ASC, [Project1].[Id1] ASC, [Project1].[C1] ASC

      導航屬性的查詢陷阱

        我們再來回顧一下導航屬性的長相(以用戶信息中的角色信息為例):

        

        可以看到,集合類的導航屬性是一個ICollection<T>類型的集合,其實現類可以是通常使用List<T>或者HashSet<T>。用了ICollection<T>,就限定了集合類的導航屬性是一個內存集合,只要用到這個導航屬性,就必須把集合中的所有數據都加載到內存中,才能進行后續操作。比如上面的例子中,我們的需求只是想知道用戶擁有角色的數量,原意只是要執行一下SQL的Count語句即可,卻想不到EF是把這個集合加載到內存中(上面的語句,是把當前用戶的所有角色信息查詢出來),再在內存中進行計數,這無形中是一個很大的資源浪費。比如在一個商城系統中,我們想了解一種商品的銷量(product.Orders.Count),那就可能把幾萬條訂單信息都加載到內存中,再進行計數,這將是災難性的資源消耗

        讀到這里,是不是對EF非常失望?

      查詢應該怎么設計

        上面的問題,在項目的開發階段,根本不是問題,因為軟件照樣能跑得起來,而且跑得好好的。但是等網站上線的時候,用戶量上來的時候,這些性能殺手就暴露無遺了。是問題,總要想辦法解決的。

        下面就來說說我的解決方案,至于方案靠譜不靠譜,讀者自行判斷。

      查詢數據集設計

        在前面的設計中,實體的數據倉儲接口已經向上層暴露了一個IQueryable<TEntity>的接口了,為什么暴露這個接口,上面也說了很多了。下面,以賬戶模塊為例,我們就來看看怎樣把這個查詢數據集往上傳遞。

        首先,不要忘了,我們的項目結構是這樣的:

         

      1. 對注入的Repository接口進行保護

        在核心業務實現類(AccountService)中,我們進行了各個相關實體的Repository接口的注入

        這里要注意,實體的Repository接口只能在業務層中使用,以防止開發者在展現層中調用增、刪、改等數據操作以實現業務,而不是在業務層中進行業務實現。因而,注入的實體的Repository接口屬性可訪問性要修改為 protected
      2. 開放查詢數據集供展現層使用

        業務層中的Repository接口都設置為 protected 了,那么在展現層無法訪問 IEntityRepository.Entities 數據集了,怎樣實現展現層的數據的查詢呢,很簡單,只要在業務接口中把 IEntityRepository.Entities 數據集 再包裝成一個IQueryable<T>的查詢數據集開發出去,就可以了。
      3. 在業務實現類中進行 IEntityRepository.Entities 數據集的包裝:


        經過這樣的封裝,在業務層中,我們可以使用 IEntityRepository.Entities 數據集 進行數據查詢,在展現層中使用業務契約中開放的數據集進行查詢。由于開發的數據集仍是IQueryable<T>類型,對EF的查詢自由度沒有損耗。

      查詢陷阱的應對方案

        對于前面提到的EF的查詢陷阱,我提出的解決方案就是

      通過IQueryable<T>的 Select(selector) 擴展方法來按需查詢。

        首先分析好當前業務中需要什么數據,要什么取什么,最后的數據用匿名對象裝載。

        比如前面提到的 輸出用戶擁有的角色數量 這個需求,實現方案如下:

        

        以上代碼執行的查詢語句如下:

      1 SELECT 
      2 [Extent1].[Id] AS [Id], 
      3 (SELECT 
      4     COUNT(1) AS [A1]
      5     FROM [dbo].[RoleMembers] AS [Extent2]
      6     WHERE [Extent1].[Id] = [Extent2].[Member_Id]) AS [C1]
      7 FROM [dbo].[Members] AS [Extent1]

         相當簡潔,這才是我們需要的效果。

      匿名對象方案與實體對象方案對比

        匿名對象的方案雖然達到了我們想要的效果,但對比實體對象方案,又有什么不同呢,下面我們來對比一下:

      1. 數據傳遞性、復用性:
        -匿名對象:基本上屬于一次性數據,無法整體傳遞,無法復用。
        +實體對象:傳遞性,復用性良好。
      2. 對重構、方法提取的支持:
        -匿名對象:由于數據無法傳遞,寫出的代碼很難進行重構,我就普寫過幾百行代碼而無法提取子方法重構的方法。
        +實體對象:數據對代碼重構、方法提取支持良好。
      3. 對緩存命中率的影響:
        -匿名對象:數據與具體的業務場景(參數、條件等)密切關聯,緩存命中率可能會較低。
        +實體對象:數據易復用,緩存命中率可能會較高。
      4. 不同層次的數據模型自動映射轉換(AutoMapper等)
        -匿名對象:屬性不定,類型不定,難以轉換。
        +實體對象:輕松實現映射轉換。
      5. 數據利用率:
        +匿名對象:數據按需獲取,利用率高,基本無浪費。
        -實體對象:數據都是整體取出,利用率低,浪費大。
      6. 程序性能影響:
        +匿名對象:容易寫出運行高效的代碼,性能良好。
        -實體對象:容易寫出性能低下的代碼。

        通過上面的對比,希望能對方案的選擇提供一些參考,至于如何取舍,最終選擇什么方案,只能自己根據業務的特點來權衡了,合適用哪個就用哪個。 

      需求實現

        前面已經說過不少次了,這里在明確的提一次,在這個架構設計中,如果現有查詢方法不能滿足業務需求,需要添加一個相應的查詢功能,你不需要到數據層去進行操作,你只需要:

      擴展IQueryable<T>,給IQueryable<T>添加一個擴展方法。

      按屬性名稱排序

         查詢離不開分頁查詢,分頁查詢之前通常會先排序,再查出指定頁的單頁數據,先來說說按屬性排序的問題吧。

        排序可以使用IQueryable<T>的OrderBy、OrderByDescending兩個擴展方法來進行,例如:

      1 source.OrderBy(m => m.AddDate).ThenByDescending(m => m.IsDeleted);

         這是系統提供的排序方法,但只支持 Expression<Func<TSource, TKey>> keySelector 類型的參數,而我們在點擊表格的表頭的時候,通常獲取到的是實體的屬性名稱的字符串,所以我們還需要擴展一個支持屬性名稱的排序方法。

        首先,定義一個類來封裝排序條件,排序條件通常包括屬性名稱與排序方向:

       1 namespace GMF.Component.Tools
       2 {
       3     /// <summary>
       4     ///     屬性排序條件信息類
       5     /// </summary>
       6     public class PropertySortCondition
       7     {
       8         /// <summary>
       9         ///     構造一個指定屬性名稱的升序排序的排序條件
      10         /// </summary>
      11         /// <param name="propertyName">排序屬性名稱</param>
      12         public PropertySortCondition(string propertyName)
      13             : this(propertyName, ListSortDirection.Ascending) { }
      14 
      15         /// <summary>
      16         ///     構造一個排序屬性名稱和排序方式的排序條件
      17         /// </summary>
      18         /// <param name="propertyName">排序屬性名稱</param>
      19         /// <param name="listSortDirection">排序方式</param>
      20         public PropertySortCondition(string propertyName, ListSortDirection listSortDirection)
      21         {
      22             PropertyName = propertyName;
      23             ListSortDirection = listSortDirection;
      24         }
      25 
      26         /// <summary>
      27         ///     獲取或設置 排序屬性名稱
      28         /// </summary>
      29         public string PropertyName { get; set; }
      30 
      31         /// <summary>
      32         ///     獲取或設置 排序方向
      33         /// </summary>
      34         public ListSortDirection ListSortDirection { get; set; }
      35     }
      36 }

         其次,我們接收的是排序條件是屬性名稱的字符串,實際還是要調用系統提供的Expression<Func<TSource, TKey>> keySelector類型參數的排序方法進行排序。所以我們還需要一個把字符串條件轉換為排序表達式,并調用系統的排序方法。

       1     private static class QueryableHelper<T>
       2     {
       3         // ReSharper disable StaticFieldInGenericType
       4         private static readonly ConcurrentDictionary<string, LambdaExpression> Cache = new ConcurrentDictionary<string, LambdaExpression>();
       5 
       6         internal static IOrderedQueryable<T> OrderBy(IQueryable<T> source, string propertyName, ListSortDirection sortDirection)
       7         {
       8             dynamic keySelector = GetLambdaExpression(propertyName);
       9             return sortDirection == ListSortDirection.Ascending
      10                 ? Queryable.OrderBy(source, keySelector)
      11                 : Queryable.OrderByDescending(source, keySelector);
      12         }
      13 
      14         internal static IOrderedQueryable<T> ThenBy(IOrderedQueryable<T> source, string propertyName, ListSortDirection sortDirection)
      15         {
      16             dynamic keySelector = GetLambdaExpression(propertyName);
      17             return sortDirection == ListSortDirection.Ascending
      18                 ? Queryable.ThenBy(source, keySelector)
      19                 : Queryable.ThenByDescending(source, keySelector);
      20         }
      21 
      22         private static LambdaExpression GetLambdaExpression(string propertyName)
      23         {
      24             if (Cache.ContainsKey(propertyName))
      25             {
      26                 return Cache[propertyName];
      27             }
      28             ParameterExpression param = Expression.Parameter(typeof (T));
      29             MemberExpression body = Expression.Property(param, propertyName);
      30             LambdaExpression keySelector = Expression.Lambda(body, param);
      31             Cache[propertyName] = keySelector;
      32             return keySelector;
      33         }
      34     }

         到此,有了前面的準備,屬性名稱的排序就非常好寫了。為了使用方便,應該做成IQueryable<T>的擴展方法:

       1     /// <summary>
       2     ///     把IQueryable[T]集合按指定屬性與排序方式進行排序
       3     /// </summary>
       4     /// <param name="source">要排序的數據集</param>
       5     /// <param name="propertyName">排序屬性名</param>
       6     /// <param name="sortDirection">排序方向</param>
       7     /// <typeparam name="T">動態類型</typeparam>
       8     /// <returns>排序后的數據集</returns>
       9     public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName,
      10         ListSortDirection sortDirection = ListSortDirection.Ascending)
      11     {
      12         PublicHelper.CheckArgument(propertyName, "propertyName");
      13         return QueryableHelper<T>.OrderBy(source, propertyName, sortDirection);
      14     }
      15 
      16     /// <summary>
      17     ///     把IQueryable[T]集合按指定屬性排序條件進行排序
      18     /// </summary>
      19     /// <typeparam name="T">動態類型</typeparam>
      20     /// <param name="source">要排序的數據集</param>
      21     /// <param name="sortCondition">列表屬性排序條件</param>
      22     /// <returns></returns>
      23     public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, PropertySortCondition sortCondition)
      24     {
      25         PublicHelper.CheckArgument(sortCondition, "sortCondition");
      26         return source.OrderBy(sortCondition.PropertyName, sortCondition.ListSortDirection);
      27     }
      28 
      29     /// <summary>
      30     ///     把IOrderedQueryable[T]集合繼續按指定屬性排序方式進行排序
      31     /// </summary>
      32     /// <typeparam name="T">動態類型</typeparam>
      33     /// <param name="source">要排序的數據集</param>
      34     /// <param name="propertyName">排序屬性名</param>
      35     /// <param name="sortDirection">排序方向</param>
      36     /// <returns></returns>
      37     public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string propertyName,
      38         ListSortDirection sortDirection = ListSortDirection.Ascending)
      39     {
      40         PublicHelper.CheckArgument(propertyName, "propertyName");
      41         return QueryableHelper<T>.ThenBy(source, propertyName, sortDirection);
      42     }
      43 
      44     /// <summary>
      45     ///     把IOrderedQueryable[T]集合繼續指定屬性排序方式進行排序
      46     /// </summary>
      47     /// <typeparam name="T">動態類型</typeparam>
      48     /// <param name="source">要排序的數據集</param>
      49     /// <param name="sortCondition">列表屬性排序條件</param>
      50     /// <returns></returns>
      51     public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, PropertySortCondition sortCondition)
      52     {
      53         PublicHelper.CheckArgument(sortCondition, "sortCondition");
      54         return source.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection);
      55     }

        這里使用了ListSortDirection來表示排序方向,當然,你也可以定義ThenByDescending擴展方法來進行反序排序。上面的排序可以寫成如下所示:

      1 source.OrderBy("AddDate").ThenBy("IsDeleted", ListSortDirection.Descending);

      分頁查詢

         下面來說說分頁查詢,通常分頁查詢的設計方法是在倉儲操作Repository中定義特定的方法來獲取分頁的數據,現在我們面對的是IQueryable<T>數據集,就不用那么麻煩了。只要定義一個專用于分頁查詢的擴展方法即可。代碼如下:

       1     /// <summary>
       2     ///     把IOrderedQueryable[T]集合繼續指定屬性排序方式進行排序
       3     /// </summary>
       4     /// <typeparam name="T">動態類型</typeparam>
       5     /// <param name="source">要排序的數據集</param>
       6     /// <param name="sortCondition">列表屬性排序條件</param>
       7     /// <returns></returns>
       8     public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, PropertySortCondition sortCondition)
       9     {
      10         PublicHelper.CheckArgument(sortCondition, "sortCondition");
      11         return source.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection);
      12     }
      13 
      14     /// <summary>
      15     ///     從指定 IQueryable[T]集合 中查詢指定分頁條件的子數據集
      16     /// </summary>
      17     /// <typeparam name="T">動態類型</typeparam>
      18     /// <param name="source">要查詢的數據集</param>
      19     /// <param name="predicate">查詢條件謂語表達式</param>
      20     /// <param name="pageIndex">分頁索引</param>
      21     /// <param name="pageSize">分頁大小</param>
      22     /// <param name="total">輸出符合條件的總記錄數</param>
      23     /// <param name="sortConditions">排序條件集合</param>
      24     /// <returns></returns>
      25     public static IQueryable<T> Where<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate, int pageIndex, int pageSize,
      26         out int total, PropertySortCondition[] sortConditions = null) where T : Entity
      27     {
      28         PublicHelper.CheckArgument(source, "source");
      29         PublicHelper.CheckArgument(predicate, "predicate");
      30         PublicHelper.CheckArgument(pageIndex, "pageIndex");
      31         PublicHelper.CheckArgument(pageSize, "pageSize");
      32 
      33         total = source.Count(predicate);
      34         if (sortConditions == null || sortConditions.Length == 0)
      35         {
      36             source = source.OrderBy(m => m.AddDate);
      37         }
      38         else
      39         {
      40             int count = 0;
      41             IOrderedQueryable<T> orderSource = null;
      42             foreach (PropertySortCondition sortCondition in sortConditions)
      43             {
      44                 orderSource = count == 0
      45                     ? source.OrderBy(sortCondition.PropertyName, sortCondition.ListSortDirection)
      46                     : orderSource.ThenBy(sortCondition.PropertyName, sortCondition.ListSortDirection);
      47                 count++;
      48             }
      49             source = orderSource;
      50         }
      51         return source != null
      52             ? source.Where(predicate).Skip((pageIndex - 1) * pageSize).Take(pageSize)
      53             : Enumerable.Empty<T>().AsQueryable();
      54     }

         這樣,要獲取某頁數據,只要調用這個擴展方法即可,跟調用系統的擴展方法一樣方便(其中total是總記錄數)。

      1     int total;
      2     var pageData = source.Where(m => m.IsDeleted, 4, 20, out total);

      查詢實戰

        下面,我們來實戰一下數據查詢。

        首先,我們要查詢的數據將用下面這個類來顯示,其中LoginLogCount為當前用戶的登錄次數,RoleNames為用戶擁有的角色名稱集合,這兩個數據都來源于與Member有關聯的其他表。

       1 namespace GMF.Demo.Site.Models
       2 {
       3     public class MemberView
       4     {
       5         public int Id { get; set; }
       6 
       7         public string UserName { get; set; }
       8 
       9         public string NickName { get; set; }
      10 
      11         public string Email { get; set; }
      12 
      13         public bool IsDeleted { get; set; }
      14 
      15         public DateTime AddDate { get; set; }
      16 
      17         public int LoginLogCount { get; set; }
      18 
      19         public IEnumerable<string> RoleNames { get; set; }
      20     }
      21 }

         為了簡化演示操作,引入分頁控件MVCPager來處理頁面上的分頁條的處理。

        Controller中代碼如下,注意數據獲取的查詢代碼:

       1 namespace GMF.Demo.Site.Web.Controllers
       2 {
       3     [Export]
       4     public class HomeController : Controller
       5     {
       6         [Import]
       7         public IAccountSiteContract AccountContract { get; set; }
       8 
       9         public ActionResult Index(int? id)
      10         {
      11             int pageIndex = id ?? 1;
      12             const int pageSize = 20;
      13             PropertySortCondition[] sortConditions = new[] { new PropertySortCondition("Id") };
      14             int total;
      15             var memberViews = AccountContract.Members.Where(m => true, pageIndex, pageSize, out total, sortConditions).Select(m => new MemberView
      16             {
      17                 UserName = m.UserName,
      18                 NickName = m.NickName,
      19                 Email = m.Email,
      20                 IsDeleted = m.IsDeleted,
      21                 AddDate = m.AddDate,
      22                 LoginLogCount = m.LoginLogs.Count,
      23                 RoleNames = m.Roles.Select(n => n.Name)
      24             });
      25             PagedList<MemberView> model = new PagedList<MemberView>(memberViews, pageIndex, pageSize, total);
      26             return View(model);
      27         }
      28     }
      29 }

        這里雖然使用了MVCPager,但并沒有使用她的分頁功能。分頁處理還是我們自己做的,只是使用了她的單頁數據模型類PageList<T>作為視圖模型 

        View代碼如下: 

      
      
       1 @using Webdiyer.WebControls.Mvc;
       2 @using GMF.Component.Tools;
       3 @model PagedList<GMF.Demo.Site.Models.MemberView>
       4 @{
       5     ViewBag.Title = "Index";
       6     Layout = "~/Views/Shared/_Layout.cshtml";
       7 }
       8 
       9 <h2>Index</h2>
      10 @if (!User.Identity.IsAuthenticated)
      11 {
      12     @Html.ActionLink("登錄", "Login", "Account")
      13 }
      14 else
      15 {
      16     <div>
      17         用戶 @User.Identity.Name 已登錄
      18         @Html.ActionLink("退出", "Logout", "Account")
      19     </div>
      20 }
      21 <table>
      22     <tr>
      23         <th>UserName</th>
      24         <th>NickName</th>
      25         <th>Email</th>
      26         <th>IsDeleted</th>
      27         <th>AddDate</th>
      28         <th>LoginLogCount</th>
      29         <th>RoleNames</th>
      30     </tr>
      31 
      32 @foreach (var item in Model) {
      33     <tr>
      34         <td>@Html.DisplayFor(modelItem => item.UserName)</td>
      35         <td>@Html.DisplayFor(modelItem => item.NickName)</td>
      36         <td>@Html.DisplayFor(modelItem => item.Email)</td>
      37         <td>@Html.DisplayFor(modelItem => item.IsDeleted)</td>
      38         <td>@Html.DisplayFor(modelItem => item.AddDate)</td>
      39         <td style="text-align:center;">
      40             @Html.DisplayFor(modelItem => item.LoginLogCount)
      41         </td>
      42         <td>@item.RoleNames.ExpandAndToString(",")</td>
      43     </tr>
      44 }
      45 </table>
      46 @Html.Pager(Model, new PagerOptions
      47 {
      48     PageIndexParameterName = "id"
      49 })
      
      

          顯示效果如下:

        

        查詢執行的SQL語句如下:

       1 SELECT 
       2 [Project2].[Id] AS [Id], 
       3 [Project2].[UserName] AS [UserName], 
       4 [Project2].[NickName] AS [NickName], 
       5 [Project2].[Email] AS [Email], 
       6 [Project2].[IsDeleted] AS [IsDeleted], 
       7 [Project2].[AddDate] AS [AddDate], 
       8 [Project2].[C2] AS [C1], 
       9 [Project2].[C1] AS [C2], 
      10 [Project2].[Name] AS [Name]
      11 FROM ( SELECT 
      12     [Limit1].[Id] AS [Id], 
      13     [Limit1].[UserName] AS [UserName], 
      14     [Limit1].[NickName] AS [NickName], 
      15     [Limit1].[Email] AS [Email], 
      16     [Limit1].[IsDeleted] AS [IsDeleted], 
      17     [Limit1].[AddDate] AS [AddDate], 
      18     [Join1].[Name] AS [Name], 
      19     CASE WHEN ([Join1].[Member_Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
      20     [Limit1].[C1] AS [C2]
      21     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]
      22         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]
      23             FROM ( SELECT 
      24                 [Extent1].[Id] AS [Id], 
      25                 [Extent1].[UserName] AS [UserName], 
      26                 [Extent1].[NickName] AS [NickName], 
      27                 [Extent1].[Email] AS [Email], 
      28                 [Extent1].[IsDeleted] AS [IsDeleted], 
      29                 [Extent1].[AddDate] AS [AddDate], 
      30                 (SELECT 
      31                     COUNT(1) AS [A1]
      32                     FROM [dbo].[LoginLogs] AS [Extent2]
      33                     WHERE [Extent1].[Id] = [Extent2].[Member_Id]) AS [C1]
      34                 FROM [dbo].[Members] AS [Extent1]
      35             )  AS [Project1]
      36         )  AS [Project1]
      37         WHERE [Project1].[row_number] > 0
      38         ORDER BY [Project1].[Id] ASC ) AS [Limit1]
      39     LEFT OUTER JOIN  (SELECT [Extent3].[Member_Id] AS [Member_Id], [Extent4].[Name] AS [Name]
      40         FROM  [dbo].[RoleMembers] AS [Extent3]
      41         INNER JOIN [dbo].[Roles] AS [Extent4] ON [Extent4].[Id] = [Extent3].[Role_Id] ) AS [Join1] ON [Limit1].[Id] = [Join1].[Member_Id]
      42 )  AS [Project2]
      43 ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC

        執行的SQL語句雖然比較復雜,但是確實是按我們的需求來進行最簡查詢的,比如我們沒有查詢Member的Password屬性,上面就沒有Password相關的語句,LoginLog的計數,Roles的Name屬性的篩選,也沒有涉及該類的其他屬性的查詢。

      源碼獲取

        為了讓大家能第一時間獲取到本架構的最新代碼,也為了方便我對代碼的管理,本系列的源碼已加入微軟的開源項目網站 http://www.codeplex.com,地址為:

        https://gmframework.codeplex.com/

        可以通過下列途徑獲取到最新代碼:

      • 如果你是本項目的參與者,可以通過VS自帶的團隊TFS直接連接到 https://tfs.codeplex.com:443/tfs/TFS17 獲取最新代碼
      • 如果你安裝有SVN客戶端(親測TortoiseSVN 1.6.7可用),可以連接到 https://gmframework.svn.codeplex.com/svn 獲取最新代
      • 如果以上條件都不滿足,你可以進入頁面 https://gmframework.codeplex.com/SourceControl/latest 查看最新代碼,也可以點擊頁面上的 Download 鏈接進行壓縮包的下載,你還可以點擊頁面上的 History 鏈接獲取到歷史版本的源代碼
      • 如果你想和大家一起學習MVC,學習EF,歡迎加入群:5008599(群發言僅限技術討論,拒絕閑聊,拒絕醬油,拒絕廣告)
      • 如果你想與我共同來完成這個開源項目,可以隨時聯系我。

      系列導航

      1. MVC實用架構設計(〇)——總體設計
      2. MVC實用架構設計(一)——項目結構搭建
      3. MVC實用架構設計(二)——使用MEF應用IOC
      4. MVC實用架構設計(三)——EF-Code First(1):Repository,UnitOfWork,DbContext
      5. MVC實用架構設計(三)——EF-Code First(2):實體映射、數據遷移,重構
      6. MVC實用架構設計(三)——EF-Code First(3):使用T4模板生成相似代碼
      7. MVC實用架構設計(三)——EF-Code First(4):數據查詢
      8. MVC實用架構設計(三)——EF-Code First(5):二級緩存
      9. MVC實體架構設計(三)——EF-Code First(6):數據更新
      10. 未完待續。。。
      posted @ 2013-07-11 00:41  郭明鋒  閱讀(26381)  評論(84)    收藏  舉報

      主站蜘蛛池模板: 久热这里只有精品视频六| 人人人爽人人爽人人av| 蜜桃传媒av免费观看麻豆| 丰满人妻被黑人猛烈进入| 久热天堂在线视频精品伊人| 成人免费精品网站在线观看影片| 国产精品v片在线观看不卡| 国产av一区二区不卡| 成人av一区二区亚洲精| 欧洲中文字幕一区二区| 日本强好片久久久久久aaa| 国模少妇无码一区二区三区| 你拍自拍亚洲一区二区三区| 精品人妻丰满久久久a| 国产精品无码a∨麻豆| 国产精品久久一区二区三区| 天堂亚洲免费视频| 野花社区www高清视频| 日韩三级一区二区在线看| 影音先锋啪啪av资源网站| 中文字幕无码色综合网| 制服丝袜人妻有码无码中文字幕| 亚洲av一区二区在线看| 人妻无码中文字幕| 国产在线乱子伦一区二区| 国产乱码日韩精品一区二区| 无码av不卡免费播放| 国产欧美日韩亚洲一区二区三区 | 中国女人熟毛茸茸A毛片| 亚洲av第三区国产精品| 日韩有码中文字幕国产| av无码av无码专区| 五月丁香激激情亚洲综合| 九九热免费在线观看视频| 精品 无码 国产观看| 4480yy亚洲午夜私人影院剧情 | japanese丰满奶水| 国产成人精彩在线视频| 国产欧美日韩免费看AⅤ视频| 2021AV在线无码最新| 精品人妻中文字幕在线|