導(dǎo)言
如我們在之前的教程里討論的那樣,分頁可以通過兩種方法來實現(xiàn):
?。?默認分頁– 你僅僅只用選中data Web control的 智能標簽的Enable Paging ; 然而,當你瀏覽頁面的時候,雖然你看到的只是一小部分數(shù)據(jù),ObjectDataSource 還是會每次都讀取所有數(shù)據(jù)
?。?自定義分頁– 通過只從數(shù)據(jù)庫讀取用戶需要瀏覽的那部分數(shù)據(jù),提高了性能. 顯然這種方法需要你做更多的工作.
默認的分頁功能非常吸引人,因為你只需要選中一個checkbox就可以完成了.但是它每次都讀取所有的數(shù)據(jù),這種方式在大數(shù)據(jù)量或者并發(fā)用戶多的情況下就不合適.在這樣的情況下,我們必須通過自定義分頁來使系統(tǒng)達到更好的性能.
自定義分頁的一個重點是要寫一個返回僅僅需要的數(shù)據(jù)的查詢語句.幸運的,Microsoft SQL Server 2005 提供了一個新的keyword,通過它我們可以寫出讀取需要的數(shù)據(jù)的查詢.在本教程里,我們將學(xué)習(xí)在GridView里如何使用Microsoft SQL Server 2005 的這個新的keyword來實現(xiàn)自定義分頁.自定義分頁和默認分頁的界面看起來一樣,但是當你從一頁轉(zhuǎn)到另一頁時,在效率上差了幾個數(shù)量級.
注意:自定義分頁帶來的性能提升程序取決于數(shù)據(jù)的總量和數(shù)據(jù)庫的負載.在本教程的最后我們會用數(shù)據(jù)來說明自定義分頁帶來的性能方面的好處.
第一步: 理解自定義分頁的過程
給數(shù)據(jù)分頁的時候,頁面顯示的數(shù)據(jù)取決于請求的是哪一頁和每頁顯示多少條.比如,想象以下我們給81個product分頁,每頁顯示10條.當我們?yōu)g覽第一頁時,我們需要的是product 1 到 product 10.當瀏覽第二頁時,我們需要的是product 11 到 product 20,以次類推.
對于需要讀取什么數(shù)據(jù)和分頁的頁面怎么顯示,有三個相關(guān)的變量:
?。?Start Row Index – 頁面里顯示數(shù)據(jù)的第一行的索引; 這個值可以通過頁的索引乘每頁顯示的記錄的條數(shù)加1得到. 例如, 如果一頁顯示10條數(shù)據(jù), 那么對第一頁來說(第一頁的索引為0), 第一行的索引為0 * 10 + 1, or 1; 對第二頁來說(索引為1), 第一行的索引為1 * 10 + 1, 即 11.
?。?Maximum Rows – 每頁顯示的最多記錄的條數(shù). 之所以稱為“maximum” rows 是由于最后一頁顯示的數(shù)據(jù)可能會比page size要小. 比如, 當以每頁10條記錄來顯示81條時, 最后一頁也就是第九頁只包含一條記錄. 沒有頁面顯示的記錄條數(shù)會大于Maximum Rows 的值.
?。?Total Record Count – 顯示數(shù)據(jù)的總條數(shù). 不需要知道頁面顯示什么數(shù)據(jù),但是記錄總數(shù)會影響到分頁. 比如, 如果對81條product記錄分頁,每頁10條,那么總頁數(shù)為9.
對默認分頁來說,Start Row Index是由頁索引和每頁的記錄數(shù)加1得到,Maximum Rows 就是每頁的記錄數(shù).使用默認分頁時,不管是呈現(xiàn)哪頁的數(shù)據(jù),都是要讀取全部的數(shù)據(jù),所有每行的索引都是已知的,這樣獲取Start Row Index變的沒有價值.而且,記錄的總條數(shù)是可以通過DataTable的總條數(shù)來獲取的.
自定義分頁只返回從Start Row Index 開始的Maximum Rows條記錄.在這里有兩個要注意的地方:
?。?我們必須把整個要分頁的數(shù)據(jù)和一個row index關(guān)聯(lián)起來,這樣才能從指定的Start Row Index 開始返回需要的數(shù)據(jù).
?。?我們需要提供用來分頁的數(shù)據(jù)的總條數(shù).
在后面的兩步里我們將寫出和上面兩點相關(guān)的SQL.除此之外,我們還將在DAL和BLL里完成相應(yīng)的方法.
第二步: 返回需要分頁的記錄的總條數(shù)
在我們學(xué)習(xí)如何返回顯示頁面需要的數(shù)據(jù)之前,我們先來看看怎么獲取數(shù)據(jù)的總條數(shù).因為在配置界面的時候需要用到這個信息.我們使用SQL的COUNT aggregate function來實現(xiàn)這個.比如,返回Products表的總記錄條數(shù),我們可以用如下的語句:
SELECT COUNT(*)
FROM Products
我們在DAL里添加一個方法來返回這個信息.這個方法名為TotalNumberOfProducts() ,它會執(zhí)行上面的SQL語句.
打開App_Code/DAL 文件夾里的 Northwind.xsd .然后在設(shè)計器里右鍵點ProductsTableAdapter ,選擇Add Query.和我們在以前的教程里學(xué)習(xí)的那樣,這樣會允許我們添加一個新的DAL方法,這個方法被調(diào)用時會執(zhí)行指定的SQL或存儲過程.和前面的TableAdapter 方法一樣,為這個添加一個SQL statement.
![](/d/20211017/1bd5c70c4a10f3d06fb876b84f8d806a.gif)
圖 1: 使用 SQL Statement
在下一個窗體我們可以指定創(chuàng)建哪種SQL .由于查詢只返回一個值–Products表的總記錄條數(shù)–我們選擇“SELECT which returns a singe value”.
![](/d/20211017/210f3316e0c9cca1a9e4d09a65437a2d.gif)
圖 2: 使用 SELECT Statement that Returns a Single Value來配置SQL
下一步是寫SQL語句.
![](/d/20211017/2671d42bf4de055201af5ef0b4712d00.gif)
圖 3: 使用SELECT COUNT(*) FROM Products 語句
最后給這個方法命名為TotalNumberOfProducts.
![](/d/20211017/9243540a902adb9d2a66373e634f0422.gif)
圖 4: 將方法命名為 TotalNumberOfProducts
點擊結(jié)束后,DAL里添加了一個TotalNumberOfProducts方法.這個方法返回的值可為空,而Count語句總是返回一個非空的值.
我們還需要在BLL中加一個方法.打開ProductsBLL類文件,添加一個TotalNumberOfProducts方法,這個方法要做的只是調(diào)用DAL的TotalNumberOfProducts方法.
public int TotalNumberOfProducts()
{
return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}
DAL的TotalNumberOfProducts方法返回一個可空的整型,而需要ProductsBLL類的TotalNumberOfProducts方法返回一個標準的整型.調(diào)用GetValueOrDefault方法,如果可為空的整型為空,則返回默認值,0.
第三步: 返回需要的數(shù)據(jù)記錄
下一步我們要在DAL和BLL里創(chuàng)建接受Start Row Index 和Maximum Rows 的方法,然后返回合適的記錄.我們首先看看需要的SQL語句.我們面臨的挑戰(zhàn)是需要為整個分頁的記錄分配索引,用來返回從Start Row Index 開始的Maximum Records number of records條記錄.
如果在數(shù)據(jù)庫表里已經(jīng)有一個列作為索引,那么一切會變的很簡單.我們首先會想到Products表的ProductID字段可以滿足這個條件,第一個Product的ProductID為1,第二個為2,以此類推.然而當一個product被刪除后,這個序列會留下間隔來,所以這個方法不行.
有兩種可以把整個要分頁的數(shù)據(jù)和一個row index關(guān)聯(lián)起來的方法.
?。?使用SQL Server 2005的ROW_NUMBER() Keyword – SQL Server 2005的新特性,它可以將記錄根據(jù)一定的順序排列,每條記錄和一個等級相關(guān) 這個等級可以用來作為每條記錄的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里用).
這個思路是,為要分頁的數(shù)據(jù)創(chuàng)建一個table變量,這個table變量里有一個作為主健的IDENTITY列.這樣需要分頁的每條記錄在table變量里就和一個row index(通過IDENTITY列)關(guān)聯(lián)起來了.一旦table變量產(chǎn)生,連接數(shù)據(jù)庫表的SELECT語句就被執(zhí)行,獲取需要的記錄.SET ROWCOUNT用來限制放到table變量里的記錄的數(shù)量.
當SET ROWCOUNT的值指定為Start Row Index 加上Maximum Rows時,這個方法的效率取決于被請求的頁數(shù).對于比較前面的頁來說– 比如開始幾頁的數(shù)據(jù)– 這種方法非常有效. 但是對接近尾部的頁來說,這種方法的效率和默認分頁時差不多.
本教程用ROW_NUMBER()來實現(xiàn)自定義分頁.如果需要知道更多的關(guān)于table變量和SET ROWCOUNT的技術(shù),請看 A More Efficient Method for Paging Through Large Result Sets.
以下語句用來使用ROW_NUMBER()將一個等級和返回的每條記錄關(guān)聯(lián):
SELECT columnList,
ROW_NUMBER() OVER(orderByClause)
FROM TableName
ROW_NUMBER()返回一個根據(jù)指定排序的表示每條記錄的等級的值.比如,我們可以用以下居于查看根據(jù)價格來排序(降序)的每個product的等級:
SELECT ProductName, UnitPrice,
ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products
圖5 是在Visual Studio里運行以上代碼的結(jié)果. 注意product根據(jù)價格排序,每行有一個等級.
![](/d/20211017/a8286055d9fff5f5a808f12999b0ca7e.gif)
圖 5: 返回的記錄里每行有一個Price Rank
注意: ROW_NUMBER() 只是 SQL Server 2005里很多排級的功能中的一種. 想了解更多的ROW_NUMBER()的討論,包括其它的排級功能,請看 Returning Ranked Results with Microsoft SQL Server 2005.
當使用OVER從句里的ORDER BY 列名(UnitPrice)來排級時,SQL Server會對結(jié)果排序.為了提升大數(shù)據(jù)量查詢時的性能,可以為用來排序的列加上非聚集索引.更多的性能考慮參考Ranking Functions and Performance in SQL Server 2005.
ROW_NUMBER()返回的等級信息無法直接在WHERE從句中使用.而在From后面的Select里可以返回ROW_NUMBER(),并在WHERE從句里使用.比如,下面的語句使用一個From后的Select返回ProductName,UnitPrice,和ROW_NUMBER()的結(jié)果,然后使用一個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
更進一步,我們可以根據(jù)這個方法返回給定Start Row Index 和Maximum Rows 的頁的數(shù)據(jù).
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的那些記錄.
我們已經(jīng)知道如何根據(jù)給定的Start Row Index 和Maximum Rows 用ROW_NUMBER()返回特定頁的數(shù)據(jù).現(xiàn)在我們需要在DAL和BLL里實現(xiàn)它.
我們首先要決定根據(jù)什么排序來分級.我們這里用product名字的字母順序.這意味著我們還不能同時實現(xiàn)排序的功能.在后面的教程里,我們將學(xué)習(xí)如何實現(xiàn)這樣的功能.
在前面我們使用SQL statement創(chuàng)建DAL方法.但是TableAdapter wizard 使用的Visual Stuido里的T-SQL 解析器不能識別帶OVER語法的ROW_NUMBER()方法.因此我們要以存儲過程來創(chuàng)建這個DAL方法.從view menu里選擇server explorer(Ctrl+Alt+S),展開NORTHWND.MDF 的節(jié)點.右鍵點擊存儲過程,選擇增加一個新的存儲過程(見圖6).
![](/d/20211017/726dab548b6961afb3131af3f8e3d5da.gif)
圖 6: 為Products分頁增加一個存儲過程
這個存儲過程帶兩個整型的輸入?yún)?shù)- @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)
創(chuàng)建完存儲過程后,花點時間測試一下.右鍵在Server Explorer 點名為GetProductsPaged的存儲過程,選擇執(zhí)行.Visual Studio 會讓你輸入?yún)?shù), @startRowIndex和@maximumRows(見圖7).輸入不同的值查看一下結(jié)果是什么.
![](/d/20211017/73633875dc00b22c2a99e162b2cd6520.gif)
圖 7: 為 @startRowIndex 和@maximumRows Parameters輸入值
輸入?yún)?shù)的值后,你會看到結(jié)果.圖8的結(jié)果為兩個參數(shù)的值都為10的結(jié)果.
![](/d/20211017/43e4fca928c00def4868e710cdc38ac9.gif)
圖 8: 將在第二頁里顯示的數(shù)據(jù)
完成存儲過程后,我們可以創(chuàng)建ProductsTableAdapter 方法了.打開Northwind.xsd ,右鍵點ProductsTableAdapter,選擇Add Query.選擇使用已經(jīng)存在的存儲過程.
![](/d/20211017/6a8e12a5b5a92dd2c09600fa38f49054.gif)
圖 9: 使用已經(jīng)存在的存儲過程創(chuàng)建DAL Method
下一步會要我們選擇要調(diào)用的存儲過程.從下拉列表里選擇GetProductsPaged .
![](/d/20211017/012c30cadc4b7f5aa0c31eb8e1c45db9.gif)
圖10: 選擇GetProductsPaged
下一步要選擇存儲過程返回的數(shù)據(jù)類型:表值,單一值,無值.由于GetProductsPaged 返回多條記錄,所以選擇表值.
![](/d/20211017/d60c140885637a97247f4a1c7dd67ac1.gif)
圖 11: 為存儲過程指定返回表值
最后給方法命名.象前面的方法一樣,選擇Fill a DataTable 和Return a DataTable,為第一個命名為FillPaged ,第二個為GetProductsPaged.
![](/d/20211017/b9568a25f27b80a2c2ce639b17c79953.gif)
圖 12: 命名方法為FillPaged 和GetProductsPaged
除了創(chuàng)建一個DAL方法返回特定頁的products外,我們需要在BLL里也這樣做.和DAL方法一樣,BLL的GetProductsPaged 方法帶兩個整型的輸入?yún)?shù),分別為Start Row Index 和Maximum Rows,并返回在指定范圍內(nèi)的記錄.在ProductsBLL 創(chuàng)建這個方法,僅僅調(diào)用DAL的GetProductsPaged 就可以了.
[System.ComponentModel.DataObjectMethodAttribute(System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}
你可以為BLL方法的參數(shù)取任何名字.但是我們馬上會看到,選擇用startRowIndex 和maximumRows 會讓我們在配置ObjectDataSource 時方便很多.
第四步: 使用自定義分頁配置ObjectDataSource
創(chuàng)建完BLL和DAL的方法后,我們可以準備創(chuàng)建一個GridView 來使用自定義分頁了.打開PagingAndSorting 文件夾里的EfficientPaging.aspx ,添加一個GridView ,然后用ObjectDataSource 來配置它.在我們以前的教程里,我們通常使用ProductsBLL 類的GetProducts 方法來配置ObjectDataSource .然而這一次,我們使用GetProductsPaged 方法.GetProducts 會返回所有的products而GetProductsPaged 只返回特定的記錄.
![](/d/20211017/d5936a53a05140deb98457d1c2dcb61c.gif)
圖 13: 使用ProductsBLL Class類的 GetProductsPaged方法 來配置ObjectDataSource
我們要創(chuàng)建一個只讀的GridView,因此在INSERT, UPDATE, 和DELETE 標簽下拉列表里選擇(None).
接下來ObjectDataSource 向?qū)屛覀冞x擇GetProductsPaged 方法的輸入?yún)?shù)startRowIndex 和maximumRows 的值.在source里選擇none.
![](/d/20211017/1cddc330883505d60ae8e72e9acc0e0b.gif)
圖 14: Sources 里選擇None
完成ObjectDataSource 向?qū)Ш?GridView 會為每個product字段創(chuàng)建一個BoundField 或CheckBoxField .可以隨意裁減GridView 的外觀.我這里選擇的是只顯示ProductName, CategoryName, SupplierName, QuantityPerUnit, 和UnitPrice BoundFields.在智能標簽里選擇支持分頁,GridView 和ObjectDataSource 的標記看起來應(yīng)該和下面差不多:
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>
如果你通過瀏覽器瀏覽頁面,你會發(fā)現(xiàn)看不到GridView .
![](/d/20211017/a64596942ae906ca26cc9ca002bc5073.gif)
圖 15: GridView 沒有被顯示
由于在ObjectDataSource 里的GetProductsPaged的startRowIndex和maximumRows的參數(shù)都為0,由SQL沒有返回任何的記錄因此GridView 看不到了.
我們需要將ObjectDataSource 配置成為自定義分頁來修補上面的問題.下面的步驟可以完成這個:
1.將ObjectDataSource的 EnablePaging 屬性設(shè)為true – 這樣表示必須傳兩個參數(shù)給SelectMethod方法: 一個指定Start Row Index (StartRowIndexParameterName), 一個指定Maximum Rows (MaximumRowsParameterName).
?。?設(shè)置 ObjectDataSource的 StartRowIndexParameterName 和MaximumRowsParameterName 屬性– StartRowIndexParameterName 和MaximumRowsParameterName 屬性是傳給SelecMethod用來自定義分頁的輸入?yún)?shù). 默認的參數(shù)名為startIndexRow and MaximumRows, 這就是在創(chuàng)建BLL里的GetProductsPaged方法時用這些給參數(shù)命名的原因 . 如果你使用了其它的參數(shù)名字–比如startIndex和maxRows–你將不得不相應(yīng)的設(shè)置ObjectDataSource的StartRowIndexParameterName和MaximumRowsParameterName(startIndex和maxRows).
3.設(shè)置 ObjectDataSource的 SelectCountMethod Property 為返回分頁記錄總數(shù)的方法的名字(TotalNumberOfProducts)–調(diào)用ProductsBLL類的TotalNumberOfProducts方法返回總的記錄數(shù) . ObjectDataSource 需要這個信息來正確的顯示頁面.
?。?從ObjectDataSource的聲明里移除startRowIndex and maximumRows asp:Parameter> 元素的標記– 當通過向?qū)渲?ObjectDataSource 時, Visual Studio 自動為GetProductsPaged方法的參數(shù)增加了兩個asp:Parameter> 元素. 設(shè)置EnablePaging 為true后, 這些參數(shù)會被自動傳遞;如果在聲明代碼里保留它們,那么ObjectDataSource會試圖傳遞4個參數(shù)給GetProductsPaged和2個參數(shù)給TotalNumberOfProducts .如果你沒有移除asp:Parameter> ,當瀏覽頁面的時候你會獲得一個象這樣的錯誤信息 : “ObjectDataSource 'ObjectDataSource1' could not find a non-generic method 'TotalNumberOfProducts' that has parameters: startRowIndex, maximumRows.”
做完這些改動后,ObjectDataSource的聲明代碼看起來應(yīng)該和下面差不多:
asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
SelectMethod="GetProductsPaged" EnablePaging="True"
SelectCountMethod="TotalNumberOfProducts">
/asp:ObjectDataSource>
注意EnablePaging和SelectCountMethod屬性已經(jīng)被設(shè)置了,asp:Parameter>被移除了.圖16是屬性窗口.
![](/d/20211017/5f1442efe4d14b6196e73e7a78d54cdc.gif)
圖16: 使用自定義分頁配置,ObjectDataSource
完成這些后,瀏覽頁面.你會看到10條product按照字母排序被列出來了.每次翻一頁看看.對用戶來說現(xiàn)在還看不出來什么差別,因為自定義分頁在大數(shù)據(jù)量的情況下效率才能顯示出來.
![](/d/20211017/d5cb67e4c2de9ed51bda6298f6b3a1e2.gif)
圖17: 根據(jù)Product的 Name排序的數(shù)據(jù)的自定義分頁
注意:自定義分頁時,ObjectDataSource的SelectCountMethod方法返回的page count值存在GridView的view state里.其它變量–PageIndex,EditIndex,SelectedIndex,DataKeys集合等–都存在control state里.control state和GridView的EnableViewState屬性無關(guān).由于PageCount的值在postback期間存在viewstate里,當你的頁面上有鏈到上一頁的link時,你需要開啟GridView的view state(如果沒有這個link,你可以禁用view state).
點上一頁link會引起postback,GridView會更新PageIndex屬性.GridView會給PageIndex賦一個小于PageCount的值.如果禁用了view state,PageCount的值在postback時會丟失,PageIndex會被賦一個最大的整型值.然后GridView在根據(jù)PageSize乘PageCount來計算starting row index時會發(fā)生OverflowException異常.
執(zhí)行自定義分頁和排序
目前我們自定義分頁時使用的排序字段是在創(chuàng)建GetProductsPaged存儲過程時寫死的.在GridView的智能標簽里有一個Enable Sorting的checkbox,不幸的是,在前面的工作里加上排序功能僅僅只能將當前頁的記錄排序.比如,按照降序查看第一頁的數(shù)據(jù),第一頁的product的順序回反轉(zhuǎn).見圖18,Carnarvon Tigers 成為第一條記錄,而在它之后的71條記錄被忽略了.排序時只排了顯示在第一頁的數(shù)據(jù).
![](/d/20211017/cb0146b9b04af5704c57cb1be47ac8bc.gif)
圖18: 只有當前頁的數(shù)據(jù)被排序了
發(fā)生這種情況的原因是調(diào)用完BLL的GetProductsPaged方法返回數(shù)據(jù)之后才排序.耳針個方法只返回特定頁的記錄.為了正確的排序,我們需要將排序表達式傳到GetProductsPaged方法里,在返回特定頁的數(shù)據(jù)前進行排序.我們將在后面的教程里完成這個功能.
執(zhí)行自定義分頁和刪除
如果你開啟GridView的刪除功能,你會發(fā)現(xiàn)刪除最后一頁的最后一條記錄時,GridView消失了,而不是正確的減掉PageIndex的值.在我們上面創(chuàng)建的GridView里開啟刪除來查看這個bug.到最后一頁(第九頁),由于我們有81條記錄,每頁顯示10條,所以你會只看到一條記錄,刪除這條記錄.
在默認分頁時,GridView會自動跳到第八頁,這也是我們想要的結(jié)果.然而在自定義分頁里, GridView卻顯示.發(fā)生這個的原因有點超出了本教程的范圍,可以看Deleting the Last Record on the Last Page from a GridView with Custom Paging.簡單的說是因為點Delete時,GridView是按這樣的步驟工作的:
刪除記錄.
按照給定的PageIndex和PageSize獲取記錄.
檢查PageIndex確保沒有超過數(shù)據(jù)源的頁的數(shù)量.如果是,GridView的PageIndex會自動減.
使用第二步獲取的記錄綁定到GridView適當?shù)捻?
問題的根源在于第二步,當獲取顯示的記錄時,使用的PageIndex仍然是最后一頁的PageIndex.因此沒有記錄被返回.在第三步里GridView判斷出PageIndex屬性大于數(shù)據(jù)源的總頁數(shù)(因為最后一頁的最后一條數(shù)據(jù)被刪除了) 就對PageIndex減1.在第四步里GridView試圖將第二步獲取的數(shù)據(jù)作為數(shù)據(jù)源進行綁定,但是沒有任何數(shù)據(jù),因此顯示的GridView不見了.在默認分頁里沒有這個問題是因為在第二步還是返回的所有數(shù)據(jù).
我們可以用兩種方法來修改這個.第一是為GridView的RowDeleted事件創(chuàng)建一個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事件創(chuàng)建一個event handler,設(shè)置AffectedRows屬性為1.在第一步刪除記錄后(在第二步之前),如果一行或多行記錄被影響,GridView會更新PageIndex的值.然而ObjectDataSource 并沒有設(shè)置AffectedRows,因此這一步不會執(zhí)行.我們需要在刪除操作成功的情況下手動設(shè)置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里找到
比較默認和自定義分頁的性能
由于自定義分頁返回需要的數(shù)據(jù),而默認分頁返回全部數(shù)據(jù),因此自定義分頁比默認分頁更有效率是非常清楚的.但是性能上的提升究竟有多少?從默認分頁換成自定義分頁有什么性能上的優(yōu)勢?
很不幸,沒有一個統(tǒng)一的答案.性能的優(yōu)勢取決于很多因素,其中最重要的是分頁記錄的數(shù)量,數(shù)據(jù)庫的負載和web server和數(shù)據(jù)庫的通信渠道.對一些小的表來說,性能的差異是可以忽略的.對成千上萬行數(shù)據(jù)的表來說,差異是非常明顯的.
我們的一篇Custom Paging in ASP.NET 2.0 with SQL Server 2005文章包含一些對比這兩種分頁技術(shù)的性能測試,用到的表有大概50,000 條記錄.在測試中我分別測試了在SQL Server里(使用SQL Profiler)和ASP.NET頁面里(使用ASP.NET's tracing features)執(zhí)行查詢的時間.注意這是在我的開發(fā)環(huán)境下單個用戶的測試結(jié)果,因此沒有模仿典型的網(wǎng)站的負載情況,結(jié)果也并不科學(xué).
|
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 |
如你所見,獲取特定頁的數(shù)據(jù)平均少了354 reads,并在恩短的時間完成.而在頁面里,自定義分頁是默認分頁所花費時間的1/100.在my article 可以看到更多的測試信息和代碼,你可以下載測試數(shù)據(jù)庫在你的環(huán)境里重新測試.
總結(jié)
默認分頁是非常容易實現(xiàn)的–你僅僅只需要選擇控件上的智能標簽里的Enable Paging checkbox –但是方便帶來的是性能的損失.在默認分頁時,用戶無論請求哪個頁面,所有的數(shù)據(jù)都會被返回,即使只有一小部分被顯示出來.為了提升性能,ObjectDataSource 提供了一個可選擇的分頁功能–自定義分頁.
自定義分頁通過只獲取需要顯示的數(shù)據(jù)來解決默認分頁的性能問題,但是使用起來更麻煩.首先,請求特定數(shù)據(jù)的查詢語句必須正確而且有效.這個可以通過很多方法來實現(xiàn).在本教程里我們使用SQL Server 2005的ROW_NUMBER來實現(xiàn)給結(jié)果分級,然后返回等級在特定范圍內(nèi)的數(shù)據(jù).其次我們需要增加一個方法來獲取需要分頁的總記錄數(shù).在創(chuàng)建完DAL和BLL方法后,我們還需要配置ObjectDataSource以使它可以獲取需要分頁的總記錄數(shù),并將正確的Row Index 和Maximum Rows 的值傳給BLL.
雖然使用自定義分頁需要一系列的操作,而且遠沒有默認分頁那么簡單.但是在大數(shù)據(jù)量的情況還是必須的.只顯示需要的數(shù)據(jù),自定義分頁可以節(jié)省很多時間,減輕數(shù)據(jù)庫的負擔(dān).
祝編程快樂!
作者簡介
Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創(chuàng)始人,自1998年以來一直應(yīng)用 微軟Web技術(shù)。Scott是個獨立的技術(shù)咨詢顧問,培訓(xùn)師,作家,最近完成了將由Sams出版社出版的新作,24小時內(nèi)精通ASP.NET 2.0。他的聯(lián)系電郵為mitchell@4guysfromrolla.com,也可以通過他的博客http://scottonwriting.net/與他聯(lián)系。
您可能感興趣的文章:- asp.net下linkbutton的前后臺使用方法
- asp.net button 綁定多個參數(shù)
- 關(guān)于asp.net button按鈕的OnClick和OnClientClick事件
- js觸發(fā)asp.net的Button的Onclick事件應(yīng)用
- ASP.NET 中 Button、LinkButton和ImageButton 三種控件的使用詳解
- 在ASP.NET 2.0中操作數(shù)據(jù)之二十三:基于用戶對修改數(shù)據(jù)進行限制
- 在ASP.NET 2.0中操作數(shù)據(jù)之二十四:分頁和排序報表數(shù)據(jù)
- 在ASP.NET 2.0中操作數(shù)據(jù)之二十六:排序自定義分頁數(shù)據(jù)
- 在ASP.NET 2.0中操作數(shù)據(jù)之二十七:創(chuàng)建自定義排序用戶界面
- 在ASP.NET 2.0中操作數(shù)據(jù)之二十八:GridView里的Button