共用方式為


有效率地分頁大量資料 (VB)

作者:Scott Mitchell

下載 PDF

使用大量數據時,數據呈現控件的默認分頁選項不適合使用,因為其基礎數據源控件會擷取所有記錄,即使只顯示一部分數據也一樣。 在這種情況下,我們必須轉換成自定義分頁。

簡介

如上一個教學課程所述,分頁可以透過下列兩種方式之一來實作:

  • 只要 檢查數據 Web 控件智慧標記中的 [啟用分頁] 選項,即可實作預設分頁;不過,每當檢視數據頁面時,ObjectDataSource 會擷取 所有 記錄,即使頁面只顯示一部分記錄也一樣
  • 自定義分頁 藉由只從資料庫擷取需要針對使用者要求之特定數據頁面顯示的記錄,來改善默認分頁的效能;不過,自定義分頁牽涉到實作比默認分頁更多的心力

由於實作容易,只要核取複選框,您就完成了! 默認分頁是吸引人的選項。 不過,擷取所有記錄的簡單方法,在分頁到足夠大量數據或具有許多並行使用者的網站時,會成為不方便的選擇。 在這種情況下,我們必須轉換成自定義分頁,以提供響應式系統。

自定義分頁的挑戰是能夠撰寫查詢,以傳回特定數據頁面所需的精確記錄集。 幸運的是,Microsoft SQL Server 2005 提供排名結果的新關鍵詞,這可讓我們撰寫可有效率地擷取適當記錄子集的查詢。 在本教學課程中,我們將瞭解如何使用這個新的 SQL Server 2005 關鍵詞,在 GridView 控件中實作自定義分頁。 雖然自定義分頁的使用者介面與預設分頁的使用者介面相同,但使用自定義分頁從一頁逐步執行到下一頁,可能比預設分頁更快。

注意

自定義分頁所呈現的確切效能提升,取決於正在分頁的記錄總數,以及放置於資料庫伺服器上的負載。 在本教學課程結束時,我們將探討一些粗略的計量,以展示透過自定義分頁取得的效能優勢。

步驟 1:瞭解自定義分頁程式

分頁數據時,頁面中顯示的精確記錄取決於所要求的數據頁面,以及每頁顯示的記錄數目。 例如,假設我們想要逐頁流覽 81 個產品,並顯示每個頁面 10 個產品。 檢視第一頁時,我們希望產品 1 到 10;在檢視第二個頁面時,我們對產品 11 到 20 感興趣,依此類故。

有三個變數決定需要擷取哪些記錄,以及如何轉譯分頁介面:

  • 開始數據列 索引 要顯示之數據頁中第一列的索引;這個索引可以藉由將頁面索引乘以每一頁顯示的記錄並新增一個來計算。 例如,一次分頁至記錄 10 時,第一頁 (其頁面索引為 0) ,起始數據列索引為 0 * 10 + 1 或 1;第二頁 (頁面索引為 1) ,起始數據列索引為 1 * 10 + 1 或 11。
  • [最大數據列 ] 每頁顯示的最大記錄數目。 自最後一頁以來,此變數稱為最大數據列,傳回的記錄可能會比頁面大小少。 例如,每頁分頁 81 個產品 10 筆記錄時,第九個和最後一頁只會有一筆記錄。 不過,沒有頁面會顯示比 [最大數據列] 值更多的記錄。
  • [記錄總數] 正在 分頁的記錄總數。 雖然此變數不需要判斷要針對指定頁面擷取哪些記錄,但它確實會決定分頁介面。 例如,如果有 81 個產品正在分頁,分頁介面會知道在分頁 UI 中顯示九個頁碼。

使用預設分頁時,[開始數據列索引] 會計算為頁面索引的乘積和頁面大小加上一個,而 [最大數據列] 只是頁面大小。 由於預設分頁會在轉譯任何數據頁時,從資料庫擷取所有記錄,因此已知每個數據列的索引,藉此讓移至 [開始數據列索引] 數據列成為一項簡單工作。 此外,[總記錄計數] 已可供使用,因為它只是 DataTable (中的記錄數目,或是用來保存資料庫結果的任何物件) 。

假設 [開始數據列索引] 和 [最大數據列] 變數,自定義分頁實作必須只傳回從 [開始數據列索引] 開始的精確記錄子集,以及之後最多傳回最大數據列數目的記錄子集。 自訂分頁提供兩項挑戰:

  • 我們必須能夠有效率地將數據列索引與整個數據分頁中的每一個數據列產生關聯,以便我們可以開始在指定的起始數據列索引處傳回記錄
  • 我們需要提供正在分頁的記錄總數

在接下來的兩個步驟中,我們將檢查回應這兩項挑戰所需的 SQL 腳本。 除了 SQL 腳本之外,我們也需要在 DAL 和 BLL 中實作方法。

步驟 2:傳回正在分頁的記錄總數

在檢查如何擷取所顯示頁面的精確記錄子集之前,讓我們先看看如何傳回正在分頁的記錄總數。 需要此資訊才能正確設定分頁用戶介面。 您可以使用聚合函數來取得COUNT特定 SQL 查詢所傳回的記錄總數。 例如,若要判斷數據表中的 Products 記錄總數,我們可以使用下列查詢:

SELECT COUNT(*)
FROM Products

讓我們將方法新增至 DAL,以傳回這項資訊。 特別是,我們將建立名為 TotalNumberOfProducts() 的 DAL 方法,以執行 SELECT 上述語句。

首先,開啟 Northwind.xsd 資料夾中的具型別 DataSet 檔案 App_Code/DAL 。 接下來,以滑鼠右鍵按兩下 ProductsTableAdapter Designer中的 ,然後選擇 [新增查詢]。 如先前教學課程中所見,這可讓我們將新的方法新增至 DAL,當叫用時,將會執行特定的 SQL 語句或預存程式。 如同先前教學課程中的 TableAdapter 方法,此教學課程選擇使用臨機操作 SQL 語句。

使用臨機操作 SQL 語句

圖 1:使用臨機操作 SQL 語句

在下一個畫面上,我們可以指定要建立的查詢類型。 由於此查詢會傳回單一純量值,因此資料表中的 Products 記錄總數會選擇會傳 SELECT 回 singe 值選項的 。

將查詢設定為使用傳回單一值的SELECT語句

圖 2:將查詢設定為使用傳回單一值的 SELECT 語句

指出要使用的查詢類型之後,接下來必須指定查詢。

使用 SELECT COUNT (*) FROM 產品查詢

圖 3:使用 SELECT COUNT (*) FROM 產品查詢

最後,指定方法的名稱。 如前所述,讓我們使用 TotalNumberOfProducts

將 DAL 方法命名為 TotalNumberOfProducts

圖 4:將 DAL 方法命名為 TotalNumberOfProducts

按兩下 [完成] 之後,精靈會將 方法新增 TotalNumberOfProducts 至 DAL。 如果 SQL 查詢的結果為 ,DAL 傳回可為 Null 的型別中的純量傳回方法為 NULL。 不過,我們的 COUNT 查詢一律會傳回非NULL 值;不論 DAL 方法傳回可為 Null 的整數。

除了 DAL 方法之外,我們也需要 BLL 中的方法。 ProductsBLL開啟類別檔案,並新增TotalNumberOfProducts方法,直接呼叫 DAL s TotalNumberOfProducts 方法:

Public Function TotalNumberOfProducts() As Integer
    Return Adapter.TotalNumberOfProducts().GetValueOrDefault()
End Function

DAL s TotalNumberOfProducts 方法會傳回可為 Null 的整數;不過,我們已建立 ProductsBLL 類別 s TotalNumberOfProducts 方法,以便傳回標準整數。 因此,我們必須讓 ProductsBLL 類別 s TotalNumberOfProducts 方法傳回 DAL s TotalNumberOfProducts 方法所傳回之可為 Null 整數的值部分。 的呼叫 GetValueOrDefault() 會傳回可為 Null 整數的值,如果存在,則為 ;不過,如果可為 Null 的整數為 null,則會傳回預設的整數值 0。

步驟 3:傳回精確的記錄子集

下一個工作是在 DAL 和 BLL 中建立方法,以接受稍早討論的起始數據列索引和最大數據列變數,並傳回適當的記錄。 在這麼做之前,讓我們先看看所需的 SQL 腳本。 我們遇到的挑戰是,我們必須能夠有效率地將索引指派給整個結果中要分頁的每一個數據列,以便我們只傳回從 [開始數據列索引] (開始的記錄,以及最多) 的 [記錄數目上限]。

如果資料庫數據表中已經有數據行做為數據列索引,則這不是一項挑戰。 第一眼,我們可能會認為 Products 數據表 ProductID 字段已足夠,因為第一個產品有 ProductID 1個、第二個是2等等。 不過,刪除產品會留下序列中的間距,使此方法成為 Null。

有兩種一般技術可用來有效率地將數據列索引與要分頁的數據產生關聯,藉此讓擷取記錄的精確子集:

  • 使用 SQL Server 2005 s ROW_NUMBER() 關鍵詞 new 來 SQL Server 2005,關鍵詞會ROW_NUMBER()根據某些順序,將排名與每個傳回的記錄產生關聯。 此排名可作為每個數據列的數據列索引。

  • 使用數據表變數和SET ROWCOUNTSQL Server 語句SET ROWCOUNT可用來指定查詢在終止前應該處理的記錄總數;數據表變數是本機 T-SQL 變數,可保存表格式數據,類似於臨時表。 此方法同樣適用於 Microsoft SQL Server 2005 和 SQL Server 2000 (,而ROW_NUMBER()此方法只適用於 2005 SQL Server 2005) 。

    這裡的概念是建立數據表變數,其數據正在分頁的數據表主鍵有一個 IDENTITY 數據行和數據行。 接下來,正在分頁數據的數據表內容會傾印到數據表變數中,藉此透過數據表中每個記錄的數據行) IDENTITY ,將循序數據列索引 (關聯。 一旦填入數據表變數, SELECT 就可以執行數據表變數上的語句,並聯結基礎表,以提取特定記錄。 SET ROWCOUNT語句用來以智慧方式限制需要傾印至數據表變數的記錄數目。

    此方法的效率是以所要求的頁碼為基礎,因為 SET ROWCOUNT 值會指派 [開始數據列索引] 的值加上 [最大數據列]。 當分頁到低編號的頁面時,例如前幾個數據頁,這個方法非常有效率。 不過,當擷取接近結尾的頁面時,它會顯示預設的分頁類似效能。

本教學課程使用 ROW_NUMBER() 關鍵詞實作自定義分頁。 如需使用數據表變數和技術 SET ROWCOUNT 的詳細資訊,請參閱 透過大型結果集分頁的更有效率的方法

關鍵詞 ROW_NUMBER() 會使用下列語法,將排名與傳回給特定排序的每個記錄相關聯:

SELECT columnList,
       ROW_NUMBER() OVER(orderByClause)
FROM TableName

ROW_NUMBER() 會傳回數值,指定每個記錄與指定順序相關的排名。 例如,若要查看每個產品的排名,從成本最高到最低,我們可以使用下列查詢:

SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products

圖 5 顯示此查詢在 Visual Studio 中透過查詢視窗執行時的結果。 請注意,產品會依價格排序,以及每個數據列的價格排名。

每個傳回記錄的價格排名都包含在內

圖 5:每個傳回記錄的價格排名都包含在內

注意

ROW_NUMBER()只是 SQL Server 2005 中提供的許多新排名函式之一。 如需的 ROW_NUMBER()更徹底討論,以及其他排名函式,請閱讀 ROW_NUMBER檔

依 子句中OVER指定ORDER BY數據行 (UnitPrice排名結果時,在上述範例) 中,SQL Server 必須排序結果。 如果數據行上有叢集索引, () 結果的排序方式,或是有涵蓋索引,但成本可能更高,則這是快速作業。 若要協助改善足夠大型查詢的效能,請考慮為排序結果的數據行新增非叢集索引。 如需效能考慮的詳細資訊,請參閱 SQL Server 2005 中的排名函式和效能

ROW_NUMBER() 傳回的排名資訊不能直接用於 WHERE 子句中。 不過,衍生數據表可用來傳回 ROW_NUMBER() 結果,然後會出現在 子句中 WHERE 。 例如,下列查詢會使用衍生數據表來傳回 ProductName 和 UnitPrice 數據行以及 ROW_NUMBER() 結果,然後使用 WHERE 子句只傳回價格排名介於 11 到 20 之間的產品:

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank BETWEEN 11 AND 20

為了進一步擴充此概念,我們可以利用此方法來擷取所需的起始數據列索引和最大數據列值的特定頁面:

SELECT PriceRank, ProductName, UnitPrice
FROM
   (SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
    FROM Products
   ) AS ProductsWithRowNumber
WHERE PriceRank > <i>StartRowIndex</i> AND
    PriceRank <= (<i>StartRowIndex</i> + <i>MaximumRows</i>)

注意

如本教學課程稍後所見,StartRowIndexObjectDataSource 提供的 索引是從零開始,而 ROW_NUMBER() SQL Server 2005 傳回的值是從 1 開始編製索引。 因此,子句會WHERE傳回嚴格大於StartRowIndex和小於或等於 StartRowIndex + MaximumRows的記錄。PriceRank

既然我們已討論如何使用 ROW_NUMBER() 來擷取特定頁面的數據,並指定 Start Row Index 和 Maximum Rows 值,我們現在必須實作此邏輯作為 DAL 和 BLL 中的方法。

建立此查詢時,我們必須決定結果的排名順序;讓我們依其名稱依字母順序排序產品。 這表示在本教學課程中使用自定義分頁實作,我們無法建立自定義分頁報表,也無法排序。 不過,在下一個教學課程中,我們將瞭解如何提供這類功能。

在上一節中,我們將 DAL 方法建立為臨機操作 SQL 語句。 不幸的是,TableAdapter 精靈所使用的Visual Studio中的 T-SQL 剖析器不喜歡 OVER 函式所使用的 ROW_NUMBER() 語法。 因此,我們必須將此 DAL 方法建立為預存程式。 從 [檢視] 功能選取 [伺服器總管] (,或按 Ctrl+Alt+S) 並展開 NORTHWND.MDF 節點。 若要新增預存程式,請以滑鼠右鍵按兩下 [預存程式] 節點,然後選擇 [新增預存程式] (請參閱圖 6) 。

新增用於透過產品分頁的新預存程式

圖 6:為透過產品分頁新增預存程式

這個預存程式應該接受兩個整數輸入參數,@startRowIndex並使用ROW_NUMBER()@maximumRows欄位排序的ProductName函式,只傳回大於指定@startRowIndex且小於或等於@maximumRow@startRowIndex + s 的數據列。 在新的預存程式中輸入下列腳本,然後按下 [儲存] 圖示,將預存程式新增至資料庫。

CREATE PROCEDURE dbo.GetProductsPaged
(
    @startRowIndex int,
    @maximumRows int
)
AS
    SELECT     ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
               UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
               CategoryName, SupplierName
FROM
   (
       SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
              UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
              (SELECT CategoryName
               FROM Categories
               WHERE Categories.CategoryID = Products.CategoryID) AS CategoryName,
              (SELECT CompanyName
               FROM Suppliers
               WHERE Suppliers.SupplierID = Products.SupplierID) AS SupplierName,
              ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank
        FROM Products
    ) AS ProductsWithRowNumbers
WHERE RowRank > @startRowIndex AND RowRank <= (@startRowIndex + @maximumRows)

建立預存程序之後,請花點時間進行測試。以滑鼠右鍵按兩下 GetProductsPaged [伺服器總管] 中的預存程式名稱,然後選擇 [執行] 選項。 Visual Studio 接著會提示您輸入參數, @startRowIndex (@maximumRow 請參閱圖 7) 。 請嘗試不同的值,並檢查結果。

輸入 <span 類別=@startRowIndex 和 @maximumRows Parameters“ />

圖 7:輸入 和 @maximumRows 參數的值@startRowIndex

選擇這些輸入參數值之後,[輸出] 視窗會顯示結果。 圖 8 顯示針對 和 @maximumRows 參數傳入 10 @startRowIndex 時的結果。

會傳回出現在第二頁數據的記錄

圖 8:在 單擊以檢視完整大小的影像) ,會傳回出現在第二頁數據的記錄 (

建立此預存程式之後,我們就可以開始建立 ProductsTableAdapter 方法。 開啟 [ Northwind.xsd 具類型的數據集],以滑鼠右鍵按兩下 ProductsTableAdapter,然後選擇 [新增查詢] 選項。 不要使用臨機操作 SQL 語句建立查詢,而是使用現有的預存程式建立查詢。

使用現有的預存程式建立 DAL 方法

圖 9:使用現有的預存程式建立 DAL 方法

接下來,系統會提示您選取要叫用的預存程式。 GetProductsPaged從下拉式清單中挑選預存程式。

從 Drop-Down 清單中選擇 GetProductsPaged 預存程式

圖 10:從 Drop-Down 列表中選擇 GetProductsPaged 預存程式

接著,下一個畫面會詢問預存程式所傳回的數據種類:表格式數據、單一值或無值。 GetProductsPaged由於預存程式可以傳回多個記錄,因此表示它會傳回表格式數據。

指出預存程式會傳回表格式數據

圖 11:指出預存程式會傳回表格式數據

最後,指出您想要建立的方法名稱。 如同先前的教學課程,請繼續進行並使用 Fill a DataTable 和 Return a DataTable 來建立方法。 將第一個方法與 FillPaged 第二 GetProductsPaged個命名為 。

將方法命名為 FillPaged 和 GetProductsPaged

圖 12:將方法命名為 FillPaged 和 GetProductsPaged

除了建立 DAL 方法以傳回特定產品頁面之外,我們也需要在 BLL 中提供這類功能。 如同 DAL 方法,BLL s GetProductsPaged 方法必須接受兩個整數輸入來指定起始數據列索引和最大值數據列,而且必須只傳回落在指定範圍內的記錄。 在 ProductsBLL 類別中建立這類 BLL 方法,只呼叫 DAL s GetProductsPaged 方法,如下所示:

<System.ComponentModel.DataObjectMethodAttribute( _
    System.ComponentModel.DataObjectMethodType.Select, False)> _
Public Function GetProductsPaged(startRowIndex As Integer, maximumRows As Integer) _
    As Northwind.ProductsDataTable
    Return Adapter.GetProductsPaged(startRowIndex, maximumRows)
End Function

您可以針對 BLL 方法的輸入參數使用任何名稱,但我們很快就會看到,選擇使用 startRowIndex ,並在 maximumRows 設定 ObjectDataSource 以使用此方法時,從額外的一些工作儲存給我們。

步驟 4:將 ObjectDataSource 設定為使用自定義分頁

使用 BLL 和 DAL 方法來存取特定子集的記錄完成時,我們就可以使用自定義分頁建立 GridView 控制項,以透過其基礎記錄頁面。 從開啟 EfficientPaging.aspx 資料夾中的頁面 PagingAndSorting 開始,將 GridView 新增至頁面,並將它設定為使用新的 ObjectDataSource 控制件。 在過去教學課程中,我們通常會將 ObjectDataSource 設定為使用 ProductsBLL 類別 s GetProducts 方法。 不過,這次我們想要改用 GetProductsPaged 方法,因為 GetProducts 方法會傳回資料庫中 的所有 產品,而 GetProductsPaged 只傳回特定的記錄子集。

將 ObjectDataSource 設定為使用 ProductsBLL 類別 s GetProductsPaged 方法

圖 13:將 ObjectDataSource 設定為使用 ProductsBLL 類別的 GetProductsPaged 方法

因為我們要建立只讀的 GridView,所以請花點時間在 INSERT、UPDATE 和 DELETE 索引卷標中設定方法下拉式清單,以 (None) 。

接下來,ObjectDataSource 精靈會提示我們輸入方法startRowIndex的來源GetProductsPagedmaximumRows輸入參數值。 這些輸入參數實際上會由 GridView 自動設定,因此只要將來源設定為 [無],然後按兩下 [完成]。

將輸入參數來源保留為 None

圖 14:將輸入參數來源保留為 None

完成 ObjectDataSource 精靈之後,GridView 將會包含每個產品數據欄位的 BoundField 或 CheckBoxField。 您可以視需要量身打造 GridView 的外觀。 我選擇只 ProductName顯示 、 CategoryNameSupplierNameQuantityPerUnitUnitPrice BoundFields。 此外,請選取其智慧標記中的 [啟用分頁] 複選框,設定 GridView 以支援分頁。 這些變更之後,GridView 和 ObjectDataSource 宣告式標記看起來應該類似下列:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
            SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged"
    TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

不過,如果您透過瀏覽器瀏覽頁面,則 GridView 找不到任何位置。

GridView 未顯示

圖 15:未顯示 GridView

GridView 遺失,因為 ObjectDataSource 目前使用 0 做為 和 maximumRows 輸入參數的值。GetProductsPagedstartRowIndex 因此,產生的 SQL 查詢不會傳回任何記錄,因此不會顯示 GridView。

若要解決此問題,我們必須將 ObjectDataSource 設定為使用自定義分頁。 這可以在下列步驟中完成:

  1. 將 ObjectDataSource 的 EnablePaging 屬性設定為 true ,這表示它必須傳遞至 SelectMethod 兩個額外參數的 ObjectDataSource:一個指定起始數據列索引 (StartRowIndexParameterName) ,另一個指定最大數據列 (MaximumRowsParameterName) 。
  2. 設定 ObjectDataSource s StartRowIndexParameterNameMaximumRowsParameterName Properties 。根據StartRowIndexParameterName 和 屬性,指出 MaximumRowsParameterName 傳入 SelectMethod 的輸入參數名稱以供自定義分頁之用。 根據預設,這些參數名稱是 startIndexRowmaximumRows,這就是為什麼在 BLL 中建立 GetProductsPaged 方法時,我使用這些值作為輸入參數。 如果您選擇對 BLL 方法 GetProductsPaged 使用不同的參數名稱,例如 startIndexmaxRows,例如,您必須 (據以設定 ObjectDataSource s StartRowIndexParameterNameMaximumRowsParameterName 屬性,例如 startIndex for StartRowIndexParameterName 和 maxRows for MaximumRowsParameterName) 。
  3. 將 ObjectDataSource s SelectCountMethod 屬性 設定為方法的 Name,這個方法會傳回正在分頁的記錄總數 (TotalNumberOfProducts) 重新叫 ProductsBLL 用類別的方法 TotalNumberOfProducts 會傳回使用執行 SELECT COUNT(*) FROM Products 查詢的 DAL 方法來分頁的記錄總數。 ObjectDataSource 需要此資訊,才能正確轉譯分頁介面。
  4. 透過精靈設定 ObjectDataSource 時,從 ObjectDataSource 宣告式標記中移除 和 maximumRows<asp:Parameter> Elements,Visual Studio 會自動為方法的輸入參數新增兩個元素。startRowIndex<asp:Parameter>GetProductsPaged 藉由將 設定 EnablePagingtrue,這些參數會自動傳遞;如果它們也出現在宣告式語法中,ObjectDataSource 會嘗試將 個參數傳遞至 GetProductsPaged 方法,並將兩個參數傳遞給 TotalNumberOfProducts 方法。 如果您忘記移除這些 <asp:Parameter> 元素,當您透過瀏覽器瀏覽頁面時,會收到錯誤訊息,例如 :ObjectDataSource 'ObjectDataSource1' 找不到具有參數的非泛型方法 'TotalNumberOfProducts':startRowIndex、maximumRows

進行這些變更之後,ObjectDataSource 的宣告式語法看起來應該如下所示:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged"
    TypeName="ProductsBLL" EnablePaging="True" SelectCountMethod="
    TotalNumberOfProducts">
</asp:ObjectDataSource>

請注意, EnablePaging 已設定 和 SelectCountMethod 屬性,並移除元素 <asp:Parameter> 。 圖 16 顯示這些變更之後 屬性視窗 的螢幕快照。

若要使用自定義分頁,請設定 ObjectDataSource 控件

圖 16:若要使用自定義分頁,請設定 ObjectDataSource 控制件

進行這些變更之後,請透過瀏覽器瀏覽此頁面。 您應該會看到列出 10 個產品,依字母順序排序。 請花一點時間逐步執行數據一次一頁。 雖然使用者對於預設分頁和自定義分頁之間的檢視方塊沒有視覺差異,但自定義分頁會透過大量數據更有效率地分頁,因為它只會擷取特定頁面需要顯示的記錄。

依產品名稱排序的數據會使用自定義分頁進行分頁

圖 17:依產品名稱排序的數據是使用自定義分頁 (按兩下來檢視大小完整的映射)

注意

使用自定義分頁時,ObjectDataSource SelectCountMethod 所傳回的頁面計數值會儲存在 GridView 的檢視狀態中。 其他 GridView 變數是 PageIndexEditIndex、、 DataKeysSelectedIndexcollection 等等會儲存在控制狀態中,不論 GridView EnableViewState 的 屬性值為何,都會保存。 PageCount由於此值會使用檢視狀態在回傳之間保存,因此使用包含連結的分頁介面將您帶至最後一頁時,必須啟用 GridView 的檢視狀態。 (如果您的分頁介面不包含最後一頁的直接連結,則您可以停用檢視狀態。)

按兩下最後一頁連結會導致回傳,並指示 GridView 更新其 PageIndex 屬性。 如果單擊最後一個頁面連結,GridView 就會將其 PageIndex 屬性指派給小於其 PageCount 屬性的值。 停用檢視狀態時,會在回傳之間遺失值, PageCountPageIndex 會改為指派最大整數值。 接下來,GridView 會嘗試藉由乘 PageSize 以 和 PageCount 屬性來判斷起始數據列索引。 這會導致 OverflowException ,因為產品超過允許的整數大小上限。

實作自定義分頁和排序

我們目前的自定義分頁實作需要在建立 GetProductsPaged 預存程式時,以靜態方式指定數據分頁的順序。 不過,您可能還注意到 GridView 的智慧標記除了 [啟用分頁] 選項之外,還包含 [啟用排序] 複選框。 可惜的是,使用目前的自定義分頁實作將排序支援新增至 GridView,只會排序目前檢視的數據頁面上的記錄。 例如,如果您將 GridView 設定為也支援分頁,然後在檢視第一頁數據時,依產品名稱以遞減順序排序,它會反轉第 1 頁的產品順序。 如圖 18 所示,這類顯示 Carnarvon Tigers 以反向字母順序排序時的第一個產品,這會忽略 Carnarvon Tigers 之後的 71 個其他產品,依字母順序排列;只有第一頁上的記錄會在排序中考慮。

只會排序目前頁面上顯示的數據

圖 18:只有目前頁面上顯示的數據已排序 (按兩下即可檢視大小完整的影像)

排序只適用於目前的數據頁面,因為排序是在從 BLL 方法 GetProductsPaged 擷取數據之後發生,而這個方法只會傳回特定頁面的那些記錄。 若要正確實作排序,我們需要將排序表達式傳遞至 GetProductsPaged 方法,以便在傳回特定數據頁面之前適當地排序數據。 我們將在下一個教學課程中瞭解如何完成此作業。

實作自定義分頁和刪除

如果您在使用自定義分頁技術來分頁的 GridView 中啟用刪除功能,您會在從最後一頁刪除最後一筆記錄時發現,GridView 會消失,而不是適當地遞減 GridView s PageIndex。 若要重現這個 Bug,請只針對我們剛才建立的教學課程啟用刪除。 移至最後一頁 (第 9 頁) ,您應該會看到單一產品,因為我們一次分頁到 81 個產品,10 個產品。 刪除此產品。

刪除最後一個產品時,GridView 應該 會自動移至第八頁,而這類功能會以預設分頁顯示。 不過,透過自定義分頁,在最後一頁刪除最後一個產品之後,GridView 只會從畫面完全消失。 發生此情況 的確切原因 遠超過本教學課程的範圍;如需此問題的來源,請參閱 從具有自定義分頁的 GridView 刪除 [最後一筆記錄 ]。 總而言之,這是因為按兩下 [刪除] 按鈕時,GridView 所執行的步驟順序如下:

  1. 刪除記錄
  2. 取得要針對指定 PageIndex 和 顯示的適當記錄 PageSize
  3. 檢查以確定 PageIndex 不會超過數據源中的數據頁數;如果這樣做,會自動遞減 GridView s PageIndex 屬性
  4. 使用步驟 2 中取得的記錄,將數據的適當頁面系結至 GridView

問題源自在步驟 2 PageIndex 中,擷取要顯示的記錄時所使用的 ,仍然是 PageIndex 唯一記錄剛刪除的最後一頁。 因此,在步驟 2 中, 不會 傳回任何記錄,因為最後一頁的數據不再包含任何記錄。 然後,在步驟 3 中,GridView 發現其 PageIndex 屬性大於數據源中的總頁數 (,因為我們刪除了最後一頁的最後一筆記錄) ,因此會遞減其 PageIndex 屬性。 在步驟 4 中,GridView 會嘗試將本身系結至步驟 2 中所擷取的數據;不過,在步驟 2 中未傳回任何記錄,因此會產生空的 GridView。 使用預設分頁時,此問題不會呈現,因為步驟 2 會從數據源擷取 所有 記錄。

若要修正此問題,我們有兩個選項。 第一個是建立 GridView RowDeleted 事件處理程式的事件處理程式,決定剛刪除的頁面中顯示多少筆記錄。 如果只有一筆記錄,則剛刪除的記錄必須是最後一筆記錄,我們需要遞減 GridView s PageIndex。 當然,只有當刪除作業實際成功時,我們才想要更新 PageIndex ,這可藉由確保 e.Exception 屬性為 null來決定。

此方法的運作方式是因為它會在 PageIndex 步驟 1 之後更新,但在步驟 2 之前更新 。 因此,在步驟 2 中,會傳回適當的記錄集。 若要達成此目的,請使用如下所示的程式代碼:

Protected Sub GridView1_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
    Handles GridView1.RowDeleted
    ' If we just deleted the last row in the GridView, decrement the PageIndex
    If e.Exception Is Nothing AndAlso GridView1.Rows.Count = 1 Then
        ' we just deleted the last row
        GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1)
    End If
End Sub

替代的因應措施是建立 ObjectDataSource 事件的 RowDeleted 事件處理程式,並將 屬性設定 AffectedRows 為 1 的值。 刪除步驟 1 中的記錄 (,但在重新擷取步驟 2) 中的數據之前,如果一或多個數據列受到作業影響,GridView 就會更新其 PageIndex 屬性。 不過, AffectedRows ObjectDataSource 不會設定 屬性,因此會省略此步驟。 執行此步驟的其中一種方式是,如果刪除作業成功完成,請手動設定 AffectedRows 屬性。 這可以使用如下所示的程式代碼來完成:

Protected Sub ObjectDataSource1_Deleted( _
    sender As Object, e As ObjectDataSourceStatusEventArgs) _
    Handles ObjectDataSource1.Deleted
    ' If we get back a Boolean value from the DeleteProduct method and it's true, then
    ' we successfully deleted the product. Set AffectedRows to 1
    If TypeOf e.ReturnValue Is Boolean AndAlso CType(e.ReturnValue, Boolean) = True Then
        e.AffectedRows = 1
    End If
End Sub

這兩個事件處理程式的程式代碼都可以在範例的程式 EfficientPaging.aspx 代碼後置類別中找到。

比較預設和自定義分頁的效能

由於自定義分頁只會擷取所需的記錄,而預設分頁會傳回所檢視 每個頁面的所有 記錄,因此自定義分頁比預設分頁更有效率。 但自定義分頁更有效率? 從預設分頁移至自定義分頁,即可看到何種效能提升?

不幸的是,這裡沒有一個大小適合所有答案。 效能提升取決於許多因素,最顯著的兩個是逐頁的記錄數目,以及放置於資料庫伺服器上的負載,以及網頁伺服器與資料庫伺服器之間的通道。 對於只有數十筆記錄的小型數據表,效能差異可能會微不足道。 對於大型數據表,有數千到數百萬個數據列,不過效能差異很嚴重。

我的文章「ASP.NET 2.0 中的自定義分頁 SQL Server 2005」包含一些效能測試,我執行了一些效能測試,以在透過具有 50,000 筆記錄的資料庫數據表分頁時,展示這兩個分頁技術之間的效能差異。 在這些測試中,我檢查了在 SQL Server 層級執行查詢的時間, (使用 SQL Profiler) ,並使用 ASP.NET 追蹤功能在 ASP.NET 頁面上執行查詢。 請記住,這些測試是在我的開發方塊上以單一作用中用戶執行,因此並不重要,而且不會模擬一般網站載入模式。 不論為何,結果都會說明使用足夠大量數據時,預設和自定義分頁的運行時間相對差異。

Avg. Duration (sec) Reads
默認分頁 SQL Profiler 1.411 383
自定義分頁 SQL Profiler 0.002 29
默認分頁 ASP.NET 追蹤 2.379 N/A
自定義分頁 ASP.NET 追蹤 0.029 N/A

如您所見,擷取需要 354 個數據的特定頁面,平均讀取較少,並以一小部分的時間完成。 在 ASP.NET 頁面上,自定義頁面能夠在使用預設分頁時,以接近 1/100 的時間 轉譯。

摘要

默認分頁是實作的子,只要在數據 Web 控件的智慧標記中核取 [啟用分頁] 複選框,但這類簡單性會以效能成本計算。 使用預設分頁時,當使用者要求 所有記錄的任何 頁面時,即使只顯示一小部分記錄也一樣。 為了對抗此效能額外負荷,ObjectDataSource 提供替代的分頁選項自定義分頁。

雖然自定義分頁只會擷取需要顯示的記錄,藉此改善預設分頁效能問題,但實作自定義分頁更相關。 首先,必須寫入查詢,以正確 (且有效率地) 存取所要求的特定記錄子集。 這可以透過數種方式來完成;我們在本教學課程中檢查的函式是使用 SQL Server 2005 的新ROW_NUMBER()函式來排名結果,然後只傳回排名落在指定範圍內的結果。 此外,我們需要新增方法來判斷正在分頁的記錄總數。 建立這些 DAL 和 BLL 方法之後,我們也需要設定 ObjectDataSource,以便判斷要分頁的記錄總數,並正確地將 [開始數據列索引] 和 [最大數據列] 值傳遞至 BLL。

雖然實作自定義分頁需要一些步驟,而且不像預設分頁一樣簡單,但自定義分頁在分頁足夠大量數據時是必要的。 如所檢查的結果所示,自定義分頁可以捨棄 ASP.NET 頁面轉譯時間的秒數,並讓資料庫伺服器上的負載減少一或多個層級。

快樂的程序設計!

關於作者

Scott Mitchell 是七份 ASP/ASP.NET 書籍的作者,以及 1998 年以來與 Microsoft Web 技術合作的 4GuysFromRolla.com 作者。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格來連線到 ,您可以在 找到http://ScottOnWriting.NET