本文轉自:http://diaosbook.com/Post/2012/9/21/linq-paging-in-entity-framework
我們知道,內存分頁效率很低。並且,如果是WebForm的項目,頁面上會有巨型ViewState,這必然不好。我自己博客用的是一個存儲過程做的分頁,用到現在都挺好,沒有任何效率問題。后來想到,既然項目里有Entity Framework,那為什么不利用EF完成分頁呢~
稍做研究之后發現,EF分頁其實很簡單。不過一樣寫文章了,光貼代碼是不負責的,還是得稍微介紹一下相關的知識。
一、頁數計算
關於分頁的基本原理,網上有很多文章,我就不多敘述了。但我發現很多介紹分頁的文章里,計算頁碼公式都掐掉了,廣為流傳的版本是:
totalPage = totalRecord / pageSize +1
稍微推敲一下就會發現,這個公式在totalRecord和pageSize正好整除時,會多一頁。比如10條記錄,每頁5條,應該是2頁的,但結果是3。然而,不寫+1又不行,因為要考慮到記錄總數小於頁尺寸的情況。現在,許多網站的分頁都有這個問題。而我自己用的是下面這個公式,無論如何都不會掐掉的:
totalPage =(totalRecord + pageSize -1)/ pageSize
注意,C#里的“/”是整除。所以這兩個公式不是數學意義上的計算公式,呵呵。
二、分頁存儲過程的原理
(題外話:SQL Server 2012有了新的分頁語法,本文只針對SQL Server 2008 R2及以往的版本)
任何分頁存儲過程的核心語句最后都會拼接為類似下面的這種SQL語句:
SELECT TOP(10)-- pageSize * FROM ( SELECT *, ROW_NUMBER() OVER(ORDER BY [Temp].[PubDate] DESC) AS [row_number] FROM [dbo].[Post] AS [Temp]) AS [Temp] WHERE [Temp].[row_number]>10--(pageIndex -1)* pageSize ORDER BY [Temp].[PubDate] DESC
這段SQL獲取的是Post表,按PubDate逆序排列后,第二頁的數據。
ROW_NUMBER()函數的作用是給查詢結果生成行號,這是為了給后面的where語句用的。這個函數的具體介紹請看MSDN:http://msdn.microsoft.com/en-us/library/ms186734.aspx
三、LINQ to SQL分頁
我們的目標很明確,就是最終要將LINQ語句翻譯到與上面相符的SQL語句。在LINQ to SQL中,Enumerable.Take<TSource>最終會翻譯為TOP(n),而Enumerable.Skip<TSource>和Take結合起來使用,就會翻譯成ROW_NUMBER()…OVER…的語句。
所以,上面貼出的SQL代碼的等價LINQ是:
Enumerable.OrderByDescending(p => p.PubDate).Skip(10).Take(10);
四、在Entity Framework中對某表分頁
用EF對某表做完映射后,我們可以封裝一個分頁方法,比如:
private static List<Post> GetPostList(int pageIndex, int pageSize) { int startRow = (pageIndex - 1) * pageSize; using (var db = new EdiBlogEntities()) { var query = db.Post.OrderByDescending(p => p.PubDate).Skip(startRow).Take(pageSize); return query.ToList(); } }
對於方法的返回類型,我選擇List是因為調用它的程序不需要訪問導航屬性。如果程序中有訪問導航屬性的需要,就不能關閉當前的database context,應該要返回IEnumerable<T>類型。
測試代碼:
static void Main(string[] args) { int pageIndex = 2; int pageSize = 10; int totalPage = (GetPostCount() + pageSize - 1) / pageSize; Console.WriteLine("List of Posts. Page {0} / {1}, showing {2} rows per page.", pageIndex, totalPage, pageSize); Console.WriteLine("---------------------------------------------------------"); foreach (var item in GetPostList(pageIndex, pageSize)) { Console.WriteLine("{0}, {1}", item.Title, item.PubDate); } } private static int GetPostCount() { using (var db = new EdiBlogEntities()) { return db.Post.Count(); } }
運行結果:
和之前的存儲過程分頁是一致的: