2013年10月16日 星期三

ASP.NET MVC 資料分頁 - 使用 PagedList.Mvc

之前有一系列的文章是在介紹在 ASP.NET MVC使用 MvcPaging 這個套件來做資料分頁,

http://kevintsengtw.blogspot.tw/search/label/資料分頁

因為文章寫得很多, MvcPaging 就跨了幾個版本,所以建議大家不要看比較早期的文章,而是看以下這些:

ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 Part.1:一般、表單(Form)
ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 Part.2:AJAX 分頁
ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 Part.3:AJAX 分頁 - jQuery
ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 Part.4:分頁進階處理
ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 - 範例程式下載
ASP.NET MVC 資料分頁 MVCPaging 2.0 應用 - 使用 DisplayTemplate 設定分頁樣式
ASP.NET MVC 資料分頁與 Route - Part.1
ASP.NET MVC 資料分頁與 Route - Part.2
ASP.NET MVC 資料分頁與 Route - Part.3

而這次要介紹的是另一個也是相當多 ASP.NET MVC 專案裡所使用的分頁套件「PagedList.Mvc」,在之前 twMVC Workshop#1 實戰營裡也有介紹,不過 Wrokshop 裡所介紹的比較詳盡與進階,而這邊只會說明初階的使用方式。

 


2013-10-19 更新:

對於執行 Skip, Take 前沒有先做 Order 排序而發生錯誤的情況「只對 LINQ to Entities 中的已排序輸入支援 'Skip' 方法。必須先呼叫 'OrderBy' 方法,然後再呼叫 'Skip' 方法。」,增加說明。

 

PagedList

https://github.com/TroyGoode/PagedList

image

在 GitHub Repository 的 READ.ME 裡有說明,可在 Visual Studio 裡透過 NuGet 安裝 PagedList.Mvc,

image

同時在 READ.ME 裡也有 Example,讓我們了解如何在 ASP.NET MVC 專案裡使用 PagedList.Mvc,

image

 

NuGet – PagedList.Mvc

http://www.nuget.org/packages/PagedList.Mvc/

目前 PagedList.Mvc 最新的版本為 4.5.0,於 2013-09-30 Release,

image

 


ASP.NET MVC 網站專案裡安裝使用

首先我這邊 Visual Studio 2012 裡已經準備好一個 ASP.NET MVC 4 網站專案,並且加入 LocalDB 以及建立好 Norwind 範例資料庫,另外使用 Entity Framework Database Fisrt 建立好 ADO.NET 實體資料模型,

image

然後建立 CustomerController 以及檢視頁面,

image

在建立好的 Customer Index 頁面裡沒有使用分頁處理,所以會將全部的 Customer 一次顯示出來,接下來我們要為專案安裝 PagedList.Mvc 套件,並且讓 Customer – Index 進行分頁顯示處理,

 

開啟 NuGet,尋找「PagedList.Mvc」然後安裝

image

image

 

Controller

開啟 CustomerController,要先在 Index 方法內進行處理,先取得分頁後的資料,原本的 Index 方法是一次將全部的 Customer 資料給取出,

image

修改如下:

記得要加入 using PagedList, 在 Index  方法增加 page 參數並且給預設值,讓預設的分頁是顯示第一頁的分頁資料;而在取得 customers 的分頁資料之前,一定要先做排序處理,否則分頁處理會報錯錯誤訊息:只對 LINQ to Entities 中的已排序輸入支援 'Skip' 方法。必須先呼叫 'OrderBy' 方法,然後再呼叫 'Skip' 方法。),另外也要記得可以不必對 customers 的查詢結果做 ToList() 處理,因為執行了 ToList() 之後就是把 customer 全部給取回,如果資料有成千上萬筆的話,每次的分頁處理都要把全部資料給取回後再取得要顯示的分頁資料,這樣相當沒有效率,所以請保持查詢結果為 IQueryable<T> 的狀態。

最後要取得分頁所要顯示的資料就要使用 ToPagedList() 方法,方法的參數就是傳入所要分頁的頁碼以及分頁資料量,這樣前端的分頁列才能夠顯示出來。

image

 

有關操作 Skip, Take 操作前沒有先做 Order 處理而產生 「只對 LINQ to Entities 中的已排序輸入支援 'Skip' 方法。必須先呼叫 'OrderBy' 方法,然後再呼叫 'Skip' 方法。」錯誤,請參考以下說明:

雖然我們是呼叫 PagedList 的 ToPagedList() 來取得分頁,我們似乎沒有在我們的程式裡去去直接執行 Skip Take 的操作,但是 PagedList 的 ToPagedList() 裡就有執行,如下:

image

MSDN - 傳回或略過序列中的項目

Take<TSource> 和 Skip<TSource> 作業僅針對已排序的集合加以妥善定義。 並未定義未排序集合或多重集 (Multiset) 的語意 (Semantics)。
由於在 SQL 中排序的限制,LINQ to SQL 會嘗試將 Take<TSource> 或 Skip<TSource> 運算子的引數排序移至運算子的結果。

 

View

完成了 Controller 的程式修改並且還沒對檢視頁面做任修改的話,在檢視頁面上是可以看到分頁的資料,但是並沒有分頁列的出現,這是因為檢視頁面還需要再加一些東西上去,

image

首先,開啟 ~/Views/Web.Config,要在這個 Web.Config 裡加入 Namespace 的使用,也可以選擇不加,這樣就必須在需要做分頁處理的檢視頁面上加入 PagedList 與 PagedList.Mvc 的 Namespace,

image

image

接著開啟 ~/Views/Customer/Index.cshtml,原本的檢視頁面上使用的 Model 型別為 IEnumable<BlogSample.Models.Customers>,

image

要改為使用 IPagedList<BlogSample.Models.Customers>,

image

另外還有個地方要修改,就是在 table 的 th 所顯示的欄位名稱,因為 Model 型別修改為使用 IPagedList<T>,所以會出現錯誤,

image

可以將 table th 裡顯示欄位名稱的部份修改成下面的內容,這樣就不會再出現錯誤了,

image

再來就是要在檢視頁面上加上分頁列,先在檢視頁面上加入 ~/Content/PagedList.css,這個 css 檔是從 NuGet 安裝 PagedList.Mvc 時就會加入的,

image

P.S. 那個 @section styles 是我有在 _Layout.cshtml 所加上的 section

 

最後在 Table 的結尾標籤下放上 PagedListPager Html Helper,這樣就完成啦!

image

執行結果

image

image

image

 

下次再來說明分頁列的一些設定方式。

 

以上

24 則留言:

  1. 謝謝你的分享
    是這樣的我遇到了一點小問題

    範例使用的是ADO.NET所產生的物件

    不過當我嘗試使用自己所建立的模組時

    發生"只對 LINQ to Entities 中的已排序輸入支援 'Skip' 方法。必須先呼叫 'OrderBy' 方法,然後再呼叫 'Skip' 方法。"
    的錯誤訊息

    想請問自訂義的model要實作哪些部分或是操作才可以套入PageList這個套件

    回覆刪除
    回覆
    1. Hello,
      我在文章裡有清楚交代「一定要先做排序處理,否則分頁處理會報錯」,
      只要是使用 LINQ 語法做 Skip Take 操作前,一定要先做 Order 排序處理,
      因為 LINQ 必須要知道你要做分頁的這一堆資料怎麼去取得分頁值,無法從未整理的資料任意取得的,
      通常做分頁處理的時候,大多數人都會遇到的問題,不論是否用 PagedList or MvcPager,
      所以只要是在操作 Skip Take 處理前,就必須要先做 Order 處理。

      MSDN - 傳回或略過序列中的項目
      http://msdn.microsoft.com/zh-tw/library/bb386988.aspx
      Take 和 Skip 作業僅針對已排序的集合加以妥善定義。 並未定義未排序集合或多重集 (Multiset) 的語意 (Semantics)。

      刪除
    2. 謝謝你的幫忙!我的問題已經解決了!

      刪除
  2. 請問一下,
    我資料比數變得比較多的時後效能變得很差,每次換頁大約需要5~6秒,
    資料筆數大約500筆左右,請問一下有沒有什麼方式可以加快速度,謝謝!

    回覆刪除
    回覆
    1. 我在做分頁的時候,取得全部資料的地方都還是 IQueryable 的型別,也就是不會直接將全部的資料庫給取出來,所以最後再頁面上所顯示的資料只會向資料庫取出該分頁的資料。
      因為你沒有說明你的程式是如何處理取出資料的部份,所以我建議你再去看一下程式,原本最後才要執行 ToList() 的地方在前面就已經先執行過了,因為 ToList() 就是要從資料庫將資料給取出來,所以要注意執行 ToList() 的時機點。

      刪除
    2. 再補充一點,使用了 PagedList.Mvc 做分頁的時候是最後才執行 ToPagedList(),如果前面沒有執行任何 ToList() 操作或是其他會讓資料全部取出的操作,那可以從資料庫的優化做起,另外還可以做的就是嘗試使用快取機制,把要作為顯示的資料先放到快取中,因為你說資料大約是 500 筆左右,這樣的資料還蠻少量的,所以可以先放到快取,之後再從快取中讀取資料做分頁處理。

      刪除
    3. 你好,
      我一開始的確是使用ToList()但是看過你的文章後我已經將程式改為IQueryable,但是兩者速度似乎沒有太大差別,
      後來程式調整後我是直接由EDMX取出資料,後放入var 中排序後最後return model 時再用ToPagedList()這樣不知道是否與你的方式相同?

      刪除
    4. Email我寄不過去耶,有沒有打錯阿?

      刪除
    5. 你好,我先寄到kevin@mvc.tw,麻煩你幫我看一下,謝謝!

      刪除
  3. 你好! 請問如果是在分頁的表單上 還必須有另一個model做validation 在填入完之後可以驗證並送出表單 要如何實作
    因為model 只能傳入 IPagedList ,所以這部份一直想不出要如何解 麻煩大大解答了,謝謝。

    回覆刪除
    回覆
    1. 我不太懂為何在一有分頁的頁面上還需要去做到到表單資料的驗證?
      如果該表單(form)是要做為查詢資料的用途,我想也不太需要作到資料輸入驗證的功能,
      「要檢驗查詢表單所輸入的資料是否符合格式」很少會去對查詢的資料做資料驗證。
      上面是我的猜測,因為你並沒有交待清楚你的使用情境,所以我無法告知你應該如何處理。

      刪除
  4. 不好意思 可能是我表達的不夠清楚
    例如 實作一個留言版的頁面 在頁面上分二個區塊 上面是 送出留言的輸入框 需要驗證使用者是否有輸入內容再送出
    下面是 從資料庫取出全部留言的訊息 並且有使用MvcPaging 實作分頁 我的問題是 這二個部份都在同一個view 但是view上面引用的model又只能使用IpagedList 那我要如何將上面的輸入表單加入驗證 不知道是否有了解我的問題 麻煩了 謝謝

    回覆刪除
    回覆
    1. hello, 把你的情境描述清楚一些就會讓我能夠了解並且提供適當的協助。
      其實很簡單,頁面上所使用的 Model 只能使用 IPagedList 嗎?
      Model 其實就是類別,ViewModel 也是類別,我們可以自訂 ViewModel 類別與成員,
      所以在 ViewModel 裡的成員可以包含你要用來輸入留言板資料的屬性以及 IPagedList,
      ViewModel 的屬性也可以加註 DataAnnotation 驗證,其實所有的資料都是靠「類別」也就是物件來做傳遞。

      刪除
  5. 喔喔 我還一直以為要有分頁只能傳ipagedlist model 感謝你的解答 我知道怎麼做了 謝謝。

    回覆刪除
  6. 你好,請問一下 (在下列方法或屬性之間的呼叫模稜兩可)
    這行var result = customers.ToPagedList(currentPage, pageSize);
    請問一下有可能是哪些狀況會導致我出現該種錯誤訊息? 謝謝您


    回覆刪除
    回覆
    1. ㄟ.... 我想我的功力還無法只靠一行的程式就可以推測出你前後程式的來龍去脈,甚至也無法在只有這樣的線索之下就推導出有哪些狀況會導致這樣錯誤的發生.....
      方法與屬性,你可以先查查看在使用 ToPagedList() 這個方法所使用到的 customers currentPage pageSize 這三個變數,有哪一個用到了屬性,因為 ToPagedList() 這是 IEnumerable(T) 型別的擴充方法,這是可以確定的,所以剩下的就是你所使用的這三個變數或是這三個變數裡有關聯到的方法。

      刪除
  7. 您好,我有個小問題,當我在同一個 controller 同時有用到
    MvcPaging 和 PagedList 的話,當我使用
    var Order_ = DB.Orders.OrderBy(x => x.OrderID);
    var Result = Order_.ToPagedList(currentPageIndex, PageSize);
    在ToPagedList這邊他都會自動的使用到PagedList的方法,無法使用到MvcPaging
    所以傳到前面的時候,都會發生錯誤,因為型別不一樣,請問有什麼辦法可以解決這個問題,
    網路上找了好多,但是都沒有看到!!!

    回覆刪除
    回覆
    1. Hello, 我的建議是... 兩個之中選一個來用(我建議 PagedList),不需要兩個都用
      另外你所說的狀況,因為我沒有試過兩個都用的情境,所以我也不知道要如何處理,Sorry

      刪除
    2. 作者已經移除這則留言。

      刪除
  8. 非常謝謝你的建議,我在和他人討論看看有無方法可以解決。

    回覆刪除
  9. 請教一下兩個問題:
    (1)我原先的Query是customers.OrderByDescending(x => x.Name),它會回傳大約700筆資料,之後改成customers.OrderByDescending(x => x.Name).ToPagedList(1,5),查詢後確實只顯示5筆資料了,但是畫面卻沒有顯示Pager,我Pager的Code如下:@Html.PagedListPager((IPagedList)Model, x => Url.Action("Index", new { page = x })),不知是哪邊沒注意到呢?

    (2)問個觀念問題,用ToPagedList(1,5)最後只抓出5筆資料,丟到View時的Model用Count()看也是5筆資料,那Pager如何得知還有其他資料而必須做出分頁呢?

    回覆刪除
    回覆
    1. 那個... 我有提供完整範例在 Github 上,請下載後去跟你的程式作比對,我的範例都是下載並重新建置後就可以執行。因為你所提供的資訊並無法讓我判斷什麼地方出現問題,所以還是請你自己去做比對判斷。
      https://github.com/kevintsengtw/PagedList.Mvc.Sample

      第二個問題,我想那個你是想問 PagedList.Mvc 怎麼做出 Pager 的嗎?
      這個也是需要請你自行去看 PagedList.Mvc 的原始碼,
      https://github.com/TroyGoode/PagedList
      重點於 IPagedList 這個型別,因為 ToPagedList() 方法執行後是產生 IPagedList 型別的資料結果,
      你所說的用 Count() 所得到結果為 5,那是該分頁的數目,並非全部資料數目也不是所有分頁數,
      答案就在 IPagedList 裡,這個留言板不適合長篇大論,另外下面的文章也可以參考
      http://kevintsengtw.blogspot.tw/2013/10/aspnet-mvc-pagedlistmvc_18.html

      刪除
    2. 作者已經移除這則留言。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。