Scott Mitchell 的ASP.NET 2.0數據教程之二十五:: 大數據量時提高分頁的效率

原文 | 下載本教程中的編碼例子 | 下載本教程的PDF版

導言


如我們在之前的教程裏討論的那樣,分頁可以通過兩種方法來實現:

  • 默認分頁– 你僅僅只用選中data Web control的 智能標籤的Enable Paging ; 然而,當你瀏覽頁面的時候,雖然你看到的只是一小部分數據,ObjectDataSource 還是會每次都讀取所有數據
  • 自定義分頁– 通過只從數據庫讀取用戶需要瀏覽的那部分數據,提高了性能. 顯然這種方法需要你做更多的工作.


默認的分頁功能非常吸引人,因爲你只需要選中一個checkbox就可以完成了.但是它每次都讀取所有的數據,這種方式在大數據量或者併發用戶多的情況下就不合適.在這樣的情況下,我們必須通過自定義分頁來使系統達到更好的性能.

自定義分頁的一個重點是要寫一個返回僅僅需要的數據的查詢語句.幸運的,Microsoft SQL Server 2005 提供了一個新的keyword,通過它我們可以寫出讀取需要的數據的查詢.在本教程裏,我們將學習在GridView裏如何使用Microsoft SQL Server 2005 的這個新的keyword來實現自定義分頁.自定義分頁和默認分頁的界面看起來一樣,但是當你從一頁轉到另一頁時,在效率上差了幾個數量級.
注意:自定義分頁帶來的性能提升程序取決於數據的總量和數據庫的負載.在本教程的最後我們會用數據來說明自定義分頁帶來的性能方面的好處.

第一步: 理解自定義分頁的過程



給數據分頁的時候,頁面顯示的數據取決於請求的是哪一頁和每頁顯示多少條.比如,想象以下我們給81個product分頁,每頁顯示10條.當我們瀏覽第一頁時,我們需要的是product 1 到 product 10.當瀏覽第二頁時,我們需要的是product 11 到 product 20,以次類推.

對於需要讀取什麼數據和分頁的頁面怎麼顯示,有三個相關的變量:

  • Start Row Index – 頁面裏顯示數據的第一行的索引; 這個值可以通過頁的索引乘每頁顯示的記錄的條數加1得到. 例如, 如果一頁顯示10條數據, 那麼對第一頁來說(第一頁的索引爲0), 第一行的索引爲0 * 10 + 1, or 1; 對第二頁來說(索引爲1), 第一行的索引爲1 * 10 + 1, 即 11.
  • Maximum Rows – 每頁顯示的最多記錄的條數. 之所以稱爲“maximum” rows 是由於最後一頁顯示的數據可能會比page size要小. 比如, 當以每頁10條記錄來顯示81條時, 最後一頁也就是第九頁只包含一條記錄. 沒有頁面顯示的記錄條數會大於Maximum Rows 的值.
  • Total Record Count – 顯示數據的總條數. 不需要知道頁面顯示什麼數據,但是記錄總數會影響到分頁. 比如, 如果對81條product記錄分頁,每頁10條,那麼總頁數爲9.

對默認分頁來說,Start Row Index是由頁索引和每頁的記錄數加1得到,Maximum Rows 就是每頁的記錄數.使用默認分頁時,不管是呈現哪頁的數據,都是要讀取全部的數據,所有每行的索引都是已知的,這樣獲取Start Row Index變的沒有價值.而且,記錄的總條數是可以通過DataTable的總條數來獲取的.
自定義分頁只返回從Start Row Index 開始的Maximum Rows條記錄.在這裏有兩個要注意的地方:

  • 我們必須把整個要分頁的數據和一個row index關聯起來,這樣才能從指定的Start Row Index 開始返回需要的數據.
  • 我們需要提供用來分頁的數據的總條數.

在後面的兩步裏我們將寫出和上面兩點相關的SQL.除此之外,我們還將在DAL和BLL裏完成相應的方法.

第二步: 返回需要分頁的記錄的總條數



在我們學習如何返回顯示頁面需要的數據之前,我們先來看看怎麼獲取數據的總條數.因爲在配置界面的時候需要用到這個信息.我們使用SQL的COUNT aggregate function來實現這個.比如,返回Products表的總記錄條數,我們可以用如下的語句:
SELECT COUNT(*)
FROM Products

我們在DAL裏添加一個方法來返回這個信息.這個方法名爲TotalNumberOfProducts() ,它會執行上面的SQL語句.

打開App_Code/DAL 文件夾裏的 Northwind.xsd .然後在設計器裏右鍵點ProductsTableAdapter ,選擇Add Query.和我們在以前的教程裏學習的那樣,這樣會允許我們添加一個新的DAL方法,這個方法被調用時會執行指定的SQL或存儲過程.和前面的TableAdapter 方法一樣,爲這個添加一個SQL statement.

圖 1: 使用 SQL Statement

在下一個窗體我們可以指定創建哪種SQL .由於查詢只返回一個值–Products表的總記錄條數–我們選擇“SELECT which returns a singe value”.

圖 2: 使用 SELECT Statement that Returns a Single Value來配置SQL

下一步是寫SQL語句.

圖 3: 使用SELECT COUNT(*) FROM Products 語句

最後給這個方法命名爲TotalNumberOfProducts.

圖 4: 將方法命名爲 TotalNumberOfProducts


點擊結束後,DAL裏添加了一個TotalNumberOfProducts方法.這個方法返回的值可爲空,而Count語句總是返回一個非空的值.
我們還需要在BLL中加一個方法.打開ProductsBLL類文件,添加一個TotalNumberOfProducts方法,這個方法要做的只是調用DAL的TotalNumberOfProducts方法.

public int TotalNumberOfProducts()
{
    return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}

DAL的TotalNumberOfProducts方法返回一個可空的整型,而需要ProductsBLL類的TotalNumberOfProducts方法返回一個標準的整型.調用GetValueOrDefault方法,如果可爲空的整型爲空,則返回默認值,0.

第三步: 返回需要的數據記錄


下一步我們要在DAL和BLL裏創建接受Start Row Index 和Maximum Rows 的方法,然後返回合適的記錄.我們首先看看需要的SQL語句.我們面臨的挑戰是需要爲整個分頁的記錄分配索引,用來返回從Start Row Index 開始的Maximum Records number of records條記錄.

如果在數據庫表裏已經有一個列作爲索引,那麼一切會變的很簡單.我們首先會想到Products表的ProductID字段可以滿足這個條件,第一個Product的ProductID爲1,第二個爲2,以此類推.然而當一個product被刪除後,這個序列會留下間隔來,所以這個方法不行.

有兩種可以把整個要分頁的數據和一個row index關聯起來的方法.

  • 使用SQL Server 2005的ROW_NUMBER() Keyword – SQL Server 2005的新特性,它可以將記錄根據一定的順序排列,每條記錄和一個等級相關 這個等級可以用來作爲每條記錄的row index.
     
  • 使用SET ROWCOUNT – SQL Server的 SET ROWCOUNT statement 可以用來指定有多少記錄需要處理; table variables 是可以存放表格式的T-SQL 變量, 和temporary tables類似. 這個方法在Microsoft SQL Server 2005 和SQL Server 2000都可以用 (ROW_NUMBER() 方法只能在SQL Server 2005裏用).


    這個思路是,爲要分頁的數據創建一個table變量,這個table變量裏有一個作爲主健的IDENTITY列.這樣需要分頁的每條記錄在table變量裏就和一個row index(通過IDENTITY列)關聯起來了.一旦table變量產生,連接數據庫表的SELECT語句就被執行,獲取需要的記錄.SET ROWCOUNT用來限制放到table變量裏的記錄的數量.
    當SET ROWCOUNT的值指定爲Start Row Index 加上Maximum Rows時,這個方法的效率取決於被請求的頁數.對於比較前面的頁來說– 比如開始幾頁的數據– 這種方法非常有效. 但是對接近尾部的頁來說,這種方法的效率和默認分頁時差不多.

本教程用ROW_NUMBER()來實現自定義分頁.如果需要知道更多的關於table變量和SET ROWCOUNT的技術,請看 A More Efficient Method for Paging Through Large Result Sets.

以下語句用來使用ROW_NUMBER()將一個等級和返回的每條記錄關聯:

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


ROW_NUMBER()返回一個根據指定排序的表示每條記錄的等級的值.比如,我們可以用以下居於查看根據價格來排序(降序)的每個product的等級:

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

圖5 是在Visual Studio裏運行以上代碼的結果. 注意product根據價格排序,每行有一個等級.

圖 5: 返回的記錄裏每行有一個Price Rank

注意: ROW_NUMBER() 只是 SQL Server 2005裏很多排級的功能中的一種. 想了解更多的ROW_NUMBER()的討論,包括其它的排級功能,請看 Returning Ranked Results with Microsoft SQL Server 2005.

當使用OVER從句裏的ORDER BY 列名(UnitPrice)來排級時,SQL Server會對結果排序.爲了提升大數據量查詢時的性能,可以爲用來排序的列加上非聚集索引.更多的性能考慮參考Ranking Functions and Performance in SQL Server 2005.

ROW_NUMBER()返回的等級信息無法直接在WHERE從句中使用.而在From後面的Select裏可以返回ROW_NUMBER(),並在WHERE從句裏使用.比如,下面的語句使用一個From後的Select返回ProductName,UnitPrice,和ROW_NUMBER()的結果,然後使用一個WHERE從句來返回price rank在11到20之間的product.

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


更進一步,我們可以根據這個方法返回給定Start Row Index 和Maximum Rows 的頁的數據.

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>)

注意:我們在本教程的後面會看到, ObjectDataSource 提供的StartRowIndex是從0開始的,而ROW_NUMBER()的值從1開始.因此,WHERE從句返回會嚴格返回PriceRank大於StartRowIndex而小於StartRowIndex+MaximumRows的那些記錄.


我們已經知道如何根據給定的Start Row Index 和Maximum Rows 用ROW_NUMBER()返回特定頁的數據.現在我們需要在DAL和BLL裏實現它.

我們首先要決定根據什麼排序來分級.我們這裏用product名字的字母順序.這意味着我們還不能同時實現排序的功能.在後面的教程裏,我們將學習如何實現這樣的功能.


在前面我們使用SQL statement創建DAL方法.但是TableAdapter  wizard 使用的Visual Stuido裏的T-SQL 解析器不能識別帶OVER語法的ROW_NUMBER()方法.因此我們要以存儲過程來創建這個DAL方法.從view menu裏選擇server explorer(Ctrl+Alt+S),展開NORTHWND.MDF 的節點.右鍵點擊存儲過程,選擇增加一個新的存儲過程(見圖6).

圖 6: 爲Products分頁增加一個存儲過程

這個存儲過程帶兩個整型的輸入參數- @startRowIndex和@maximumRows- 並用ROW_NUMBER()以ProductName字段排序,返回那些大於@startRowIndex並小於等於@startRowIndex+@maximumRows的記錄.將以下代碼加到存儲過程裏,然後保存.

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)


創建完存儲過程後,花點時間測試一下.右鍵在Server Explorer 點名爲GetProductsPaged的存儲過程,選擇執行.Visual Studio 會讓你輸入參數, @startRowIndex和@maximumRows(見圖7).輸入不同的值查看一下結果是什麼.

圖 7: 爲 @startRowIndex 和@maximumRows Parameters輸入值

輸入參數的值後,你會看到結果.圖8的結果爲兩個參數的值都爲10的結果.

圖 8: 將在第二頁裏顯示的數據


完成存儲過程後,我們可以創建ProductsTableAdapter 方法了.打開Northwind.xsd ,右鍵點ProductsTableAdapter,選擇Add Query.選擇使用已經存在的存儲過程.

圖 9: 使用已經存在的存儲過程創建DAL Method

下一步會要我們選擇要調用的存儲過程.從下拉列表裏選擇GetProductsPaged .

圖10: 選擇GetProductsPaged

下一步要選擇存儲過程返回的數據類型:表值,單一值,無值.由於GetProductsPaged 返回多條記錄,所以選擇表值.

圖 11: 爲存儲過程指定返回表值

最後給方法命名.象前面的方法一樣,選擇Fill a DataTable 和Return a DataTable,爲第一個命名爲FillPaged ,第二個爲GetProductsPaged.

圖 12: 命名方法爲FillPaged 和GetProductsPaged

除了創建一個DAL方法返回特定頁的products外,我們需要在BLL裏也這樣做.和DAL方法一樣,BLL的GetProductsPaged 方法帶兩個整型的輸入參數,分別爲Start Row Index 和Maximum Rows,並返回在指定範圍內的記錄.在ProductsBLL 創建這個方法,僅僅調用DAL的GetProductsPaged 就可以了.

[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
    return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}



你可以爲BLL方法的參數取任何名字.但是我們馬上會看到,選擇用startRowIndex 和maximumRows 會讓我們在配置ObjectDataSource 時方便很多.

第四步: 使用自定義分頁配置ObjectDataSource


創建完BLL和DAL的方法後,我們可以準備創建一個GridView 來使用自定義分頁了.打開PagingAndSorting 文件夾裏的EfficientPaging.aspx ,添加一個GridView ,然後用ObjectDataSource 來配置它.在我們以前的教程裏,我們通常使用ProductsBLL 類的GetProducts 方法來配置ObjectDataSource .然而這一次,我們使用GetProductsPaged 方法.GetProducts 會返回所有的products而GetProductsPaged 只返回特定的記錄.

圖 13: 使用ProductsBLL Class類的 GetProductsPaged方法 來配置ObjectDataSource

我們要創建一個只讀的GridView,因此在INSERT, UPDATE, 和DELETE 標籤下拉列表裏選擇(None).

接下來ObjectDataSource 嚮導會讓我們選擇GetProductsPaged 方法的輸入參數startRowIndex 和maximumRows 的值.在source裏選擇none.

圖 14: Sources 裏選擇None

完成ObjectDataSource 嚮導後,GridView 會爲每個product字段創建一個BoundField 或CheckBoxField .可以隨意裁減GridView 的外觀.我這裏選擇的是隻顯示ProductName, CategoryName, SupplierName, QuantityPerUnit, 和UnitPrice BoundFields.在智能標籤裏選擇支持分頁,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:ObjectDataSoure>



如果你通過瀏覽器瀏覽頁面,你會發現看不到GridView .

圖 15: GridView 沒有被顯示

由於在ObjectDataSource 裏的GetProductsPaged的startRowIndex和maximumRows的參數都爲0,由SQL沒有返回任何的記錄因此GridView 看不到了.


我們需要將ObjectDataSource 配置成爲自定義分頁來修補上面的問題.下面的步驟可以完成這個:

  1. 將ObjectDataSource的 EnablePaging 屬性設爲true – 這樣表示必須傳兩個參數給SelectMethod方法: 一個指定Start Row Index (StartRowIndexParameterName), 一個指定Maximum Rows (MaximumRowsParameterName).
  2. 設置 ObjectDataSource的 StartRowIndexParameterName 和MaximumRowsParameterName 屬性– StartRowIndexParameterName 和MaximumRowsParameterName 屬性是傳給SelecMethod用來自定義分頁的輸入參數. 默認的參數名爲startIndexRow and MaximumRows, 這就是在創建BLL裏的GetProductsPaged方法時用這些給參數命名的原因 . 如果你使用了其它的參數名字–比如startIndex和maxRows–你將不得不相應的設置ObjectDataSource的StartRowIndexParameterName和MaximumRowsParameterName(startIndex和maxRows).
  3. 設置 ObjectDataSource的 SelectCountMethod Property 爲返回分頁記錄總數的方法的名字(TotalNumberOfProducts)–調用ProductsBLL類的TotalNumberOfProducts方法返回總的記錄數 . ObjectDataSource 需要這個信息來正確的顯示頁面. 
  4. 從ObjectDataSource的聲明裏移除startRowIndex and maximumRows <asp:Parameter> 元素的標記– 當通過嚮導配置 ObjectDataSource 時, Visual Studio 自動爲GetProductsPaged方法的參數增加了兩個<asp:Parameter> 元素. 設置EnablePaging 爲true後, 這些參數會被自動傳遞;如果在聲明代碼裏保留它們,那麼ObjectDataSource會試圖傳遞4個參數給GetProductsPaged和2個參數給TotalNumberOfProducts .如果你沒有移除<asp:Parameter> ,當瀏覽頁面的時候你會獲得一個象這樣的錯誤信息 : “ObjectDataSource 'ObjectDataSource1' could not find a non-generic method 'TotalNumberOfProducts' that has parameters: startRowIndex, maximumRows.”


做完這些改動後,ObjectDataSource的聲明代碼看起來應該和下面差不多:

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

注意EnablePaging和SelectCountMethod屬性已經被設置了,<asp:Parameter>被移除了.圖16是屬性窗口.

圖16: 使用自定義分頁配置,ObjectDataSource



完成這些後,瀏覽頁面.你會看到10條product按照字母排序被列出來了.每次翻一頁看看.對用戶來說現在還看不出來什麼差別,因爲自定義分頁在大數據量的情況下效率才能顯示出來.

圖17: 根據Product的 Name排序的數據的自定義分頁

注意:自定義分頁時,ObjectDataSource的SelectCountMethod方法返回的page count值存在GridView的view state裏.其它變量–PageIndex,EditIndex,SelectedIndex,DataKeys集合等–都存在control state裏.control state和GridView的EnableViewState屬性無關.由於PageCount的值在postback期間存在viewstate裏,當你的頁面上有鏈到上一頁的link時,你需要開啓GridView的view state(如果沒有這個link,你可以禁用view state).

點上一頁link會引起postback,GridView會更新PageIndex屬性.GridView會給PageIndex賦一個小於PageCount的值.如果禁用了view state,PageCount的值在postback時會丟失,PageIndex會被賦一個最大的整型值.然後GridView在根據PageSize乘PageCount來計算starting row index時會發生OverflowException異常.

執行自定義分頁和排序



目前我們自定義分頁時使用的排序字段是在創建GetProductsPaged存儲過程時寫死的.在GridView的智能標籤裏有一個Enable Sorting的checkbox,不幸的是,在前面的工作里加上排序功能僅僅只能將當前頁的記錄排序.比如,按照降序查看第一頁的數據,第一頁的product的順序回反轉.見圖18,Carnarvon Tigers 成爲第一條記錄,而在它之後的71條記錄被忽略了.排序時只排了顯示在第一頁的數據.

圖18: 只有當前頁的數據被排序了

發生這種情況的原因是調用完BLL的GetProductsPaged方法返回數據之後才排序.耳針個方法只返回特定頁的記錄.爲了正確的排序,我們需要將排序表達式傳到GetProductsPaged方法裏,在返回特定頁的數據前進行排序.我們將在後面的教程裏完成這個功能.

執行自定義分頁和刪除


如果你開啓GridView的刪除功能,你會發現刪除最後一頁的最後一條記錄時,GridView消失了,而不是正確的減掉PageIndex的值.在我們上面創建的GridView裏開啓刪除來查看這個bug.到最後一頁(第九頁),由於我們有81條記錄,每頁顯示10條,所以你會只看到一條記錄,刪除這條記錄.

在默認分頁時,GridView會自動跳到第八頁,這也是我們想要的結果.然而在自定義分頁裏, GridView卻顯示.發生這個的原因有點超出了本教程的範圍,可以看Deleting the Last Record on the Last Page from a GridView with Custom Paging.簡單的說是因爲點Delete時,GridView是按這樣的步驟工作的:

  1. 刪除記錄.
  2. 按照給定的PageIndex和PageSize獲取記錄.
  3. 檢查PageIndex確保沒有超過數據源的頁的數量.如果是,GridView的PageIndex會自動減.
  4. 使用第二步獲取的記錄綁定到GridView適當的頁.



問題的根源在於第二步,當獲取顯示的記錄時,使用的PageIndex仍然是最後一頁的PageIndex.因此沒有記錄被返回.在第三步裏GridView判斷出PageIndex屬性大於數據源的總頁數(因爲最後一頁的最後一條數據被刪除了) 就對PageIndex減1.在第四步裏GridView試圖將第二步獲取的數據作爲數據源進行綁定,但是沒有任何數據,因此顯示的GridView不見了.在默認分頁裏沒有這個問題是因爲在第二步還是返回的所有數據.

 
我們可以用兩種方法來修改這個.第一是爲GridView的RowDeleted事件創建一個event handler
來判斷在刪除頁裏有多少條記錄,如果只有一條,那麼這條肯定是最後一條,我們需要爲PageIndex減1.當然我們希望只在刪除成功後來修改PageIndex的值.我們需要用e.Exception屬性是否爲空來判斷.


這個方法之所以起作用是因爲它在第一步和第二步之間修改了PageIndex的值.因此在第二步里正確的記錄會被返回.見如下代碼:

protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
    // If we just deleted the last row in the GridView, decrement the PageIndex
    if (e.Exception == null && GridView1.Rows.Count == 1)
        // we just deleted the last row
        GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1);
}


另外一種辦法是爲ObjectDataSource的RowDeleted事件創建一個event handler,設置AffectedRows屬性爲1.在第一步刪除記錄後(在第二步之前),如果一行或多行記錄被影響,GridView會更新PageIndex的值.然而ObjectDataSource 並沒有設置AffectedRows,因此這一步不會執行.我們需要在刪除操作成功的情況下手動設置AffectedRows.見下面的代碼:

protected void ObjectDataSource1_Deleted(
    object sender, ObjectDataSourceStatusEventArgs e)
{
    // 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 (e.ReturnValue is bool && ((bool)e.ReturnValue) == true)
        e.AffectedRows = 1;
}

這些代碼都可以在EfficientPaging.aspx的code-behind class裏找到

比較默認和自定義分頁的性能



由於自定義分頁返回需要的數據,而默認分頁返回全部數據,因此自定義分頁比默認分頁更有效率是非常清楚的.但是性能上的提升究竟有多少?從默認分頁換成自定義分頁有什麼性能上的優勢?

很不幸,沒有一個統一的答案.性能的優勢取決於很多因素,其中最重要的是分頁記錄的數量,數據庫的負載和web server和數據庫的通信渠道.對一些小的表來說,性能的差異是可以忽略的.對成千上萬行數據的表來說,差異是非常明顯的.


我們的一篇Custom Paging in ASP.NET 2.0 with SQL Server 2005文章包含一些對比這兩種分頁技術的性能測試,用到的表有大概50,000 條記錄.在測試中我分別測試了在SQL Server裏(使用SQL Profiler)和ASP.NET頁面裏(使用ASP.NET’s tracing features)執行查詢的時間.注意這是在我的開發環境下單個用戶的測試結果,因此沒有模仿典型的網站的負載情況,結果也並不科學.

  Avg. Duration (sec) Reads
Default Paging – SQL Profiler 1.411 383
Custom Paging – SQL Profiler 0.002 29
Default Paging – ASP.NET Trace 2.379 N/A
Custom Paging – ASP.NET Trace 0.029 N/A


如你所見,獲取特定頁的數據平均少了354 reads,並在恩短的時間完成.而在頁面裏,自定義分頁是默認分頁所花費時間的1/100.在my article 可以看到更多的測試信息和代碼,你可以下載測試數據庫在你的環境裏重新測試.

總結



默認分頁是非常容易實現的–你僅僅只需要選擇控件上的智能標籤裏的Enable Paging checkbox –但是方便帶來的是性能的損失.在默認分頁時,用戶無論請求哪個頁面,所有的數據都會被返回,即使只有一小部分被顯示出來.爲了提升性能,ObjectDataSource 提供了一個可選擇的分頁功能–自定義分頁.

自定義分頁通過只獲取需要顯示的數據來解決默認分頁的性能問題,但是使用起來更麻煩.首先,請求特定數據的查詢語句必須正確而且有效.這個可以通過很多方法來實現.在本教程裏我們使用SQL Server 2005的ROW_NUMBER來實現給結果分級,然後返回等級在特定範圍內的數據.其次我們需要增加一個方法來獲取需要分頁的總記錄數.在創建完DAL和BLL方法後,我們還需要配置ObjectDataSource以使它可以獲取需要分頁的總記錄數,並將正確的Row Index 和Maximum Rows 的值傳給BLL.

雖然使用自定義分頁需要一系列的操作,而且遠沒有默認分頁那麼簡單.但是在大數據量的情況還是必須的.只顯示需要的數據,自定義分頁可以節省很多時間,減輕數據庫的負擔.


祝編程快樂!


作者簡介

Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用 微軟Web技術。Scott是個獨立的技術諮詢顧問,培訓師,作家,最近完成了將由Sams出版社出版的新作,24小時內精通ASP.NET 2.0。他的聯繫電郵爲[email protected],也可以通過他的博客http://scottonwriting.net/與他聯繫。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章