詳解ASP.NET在SQL Server 2005上自定義分頁(yè)
WEB開(kāi)發(fā)中普遍會(huì)用頁(yè)面來(lái)顯示數(shù)據(jù)。比起整頁(yè)顯示一張報(bào)表或者一張數(shù)據(jù)表的數(shù)據(jù)給用戶,開(kāi)發(fā)者經(jīng)常用到的是分頁(yè)顯示,每頁(yè)只顯示部分?jǐn)?shù)據(jù),用翻頁(yè)來(lái)控制。在ASPV.NET 1.X里,DataGrid控件使翻頁(yè)顯示變得簡(jiǎn)單—只需要把屬性AllowPaging設(shè)置為”true”,并在PageIndexChanged事件中加少量幾行的代碼就可以實(shí)現(xiàn)!ASP.NET 2.0中的控件GridView使事件也簡(jiǎn)單化了,只需要在GridView的智能面板里把“允許分頁(yè)”選中,不需要一行代碼就可以實(shí)現(xiàn)。
當(dāng)然了,生活中沒(méi)有任何事是容易就能做好的。你需要權(quán)衡選中一個(gè)復(fù)選框就能實(shí)現(xiàn)的分頁(yè)方案(或者用DataGrid,寫(xiě)幾行代碼的實(shí)現(xiàn)方案)的性能。如果不選中分頁(yè),DataGrid和GridView使用默認(rèn)分頁(yè),簡(jiǎn)單的把所有數(shù)據(jù)從頭到尾地顯示在一張網(wǎng)頁(yè)上。當(dāng)數(shù)據(jù)量小時(shí),幾十一百或類似的數(shù)量時(shí),效率性能不會(huì)太明顯。但是,如果你想像這樣用默認(rèn)的分頁(yè)方法顯示上千萬(wàn)、以至幾十萬(wàn)的書(shū)時(shí)就不可行了。
取代默認(rèn)分頁(yè)的方法就是自定義分頁(yè),你要做的工作就是用代碼來(lái)判斷并把正確的分頁(yè)數(shù)據(jù)取出,可能會(huì)費(fèi)點(diǎn)事,但對(duì)于應(yīng)付如此龐大的數(shù)據(jù)量來(lái)說(shuō),絕對(duì)值得。我們來(lái)看在ASP.NET 2.0中如何利用SQL Server 2005的新特性 ROW_NUMBER()來(lái)實(shí)現(xiàn)自定義分頁(yè)。(更多關(guān)于SQL SERVER的新特性說(shuō)明,請(qǐng)看利用Microsoft SQL SERVER 2005返回列值。)
默認(rèn)分頁(yè)與自定義分頁(yè)對(duì)比
在ASP.NET 2.0里GridView(或ASP.NET 1.X里的DataGrid)提供兩種分頁(yè)模型:默認(rèn)分頁(yè)與自定義分頁(yè)。這兩種模型在性能與易用性上提供了折中的方案。SqlDataSource控件使用默認(rèn)分頁(yè)(盡管你可以在自定義分頁(yè)了使用它);ObjectDataSource默認(rèn)使用默認(rèn)分頁(yè)模型,不過(guò)有個(gè)簡(jiǎn)單的配置可以讓它使用自定義分頁(yè)。心里要時(shí)刻記得GridView僅僅是顯示數(shù)據(jù);它才是GridView負(fù)責(zé)從數(shù)據(jù)庫(kù)中檢索數(shù)據(jù)的數(shù)據(jù)源控件。
使用默認(rèn)分頁(yè),每次打開(kāi)新頁(yè)顯并顯示時(shí),都要從GridView的數(shù)據(jù)源控件中獲得所有數(shù)據(jù)。一旦全部的數(shù)據(jù)返回,GridView就把所有的數(shù)據(jù)全顯示在頁(yè)面上,于是用戶看到一張頁(yè)面上顯示了如此多的數(shù)據(jù)。關(guān)鍵要理解這里,無(wú)論何時(shí)當(dāng)用戶訪問(wèn)***頁(yè)或翻到其他頁(yè)時(shí),所有的數(shù)據(jù)都會(huì)被重新加載一遍。
舉個(gè)例子,比如說(shuō)你在一家電子商務(wù)公司上班,你想讓用戶分頁(yè)查看你們公司所銷售的150中產(chǎn)品。并且,你想每頁(yè)只顯示10條數(shù)據(jù)?,F(xiàn)在,當(dāng)一個(gè)用戶訪問(wèn)網(wǎng)頁(yè)時(shí),所有150條數(shù)據(jù)都被數(shù)據(jù)源空間返回過(guò)來(lái)。但GridView只顯示***個(gè)10條數(shù)據(jù)(產(chǎn)品1到產(chǎn)品10)。再想象一下,當(dāng)這個(gè)用戶翻看第二頁(yè)的數(shù)據(jù)時(shí),這會(huì)引發(fā)一個(gè)回發(fā)的事件,而且在這個(gè)時(shí)候GridView又會(huì)從數(shù)據(jù)源控件中獲得所有的150條記錄,但這時(shí)只需要顯示第二個(gè)10條(產(chǎn)品11到產(chǎn)品20)。
緩存與SqlDataSource
SqlDataCourse在對(duì)屬性EnableCaching做簡(jiǎn)單設(shè)置后允許數(shù)據(jù)集緩存數(shù)據(jù)。對(duì)于一個(gè)緩存的數(shù)據(jù)集,翻頁(yè)時(shí)不需要再訪問(wèn)數(shù)據(jù)庫(kù),從分頁(yè)開(kāi)始到結(jié)束都緩存在內(nèi)存里。然而,初始化頁(yè)面時(shí)相同的問(wèn)題發(fā)生了—所有的數(shù)據(jù)必須載入到緩存的數(shù)據(jù)集里去。此外,這樣的話你還必須擔(dān)憂那些過(guò)期的數(shù)據(jù)(雖然你使用了SQL cache dependencies ,但在這里已經(jīng)沒(méi)意義了)。
在我不太科學(xué)的測(cè)試下,發(fā)現(xiàn)緩存數(shù)據(jù)和自定義分頁(yè)的速度差2倍以上……盡管,你看到緩存方式接近不緩存的方式。(但它始終沒(méi)有超過(guò)自定義分頁(yè)?。?/P>
更多關(guān)于SqlDataSource的數(shù)據(jù)集緩存,請(qǐng)看利用SqlDataSource緩存數(shù)據(jù).
使用自定義分頁(yè),你,開(kāi)發(fā)者,需要做一些工作,但比起盲目的把GridView綁定到一個(gè)數(shù)據(jù)源控件,并且選中“允許分頁(yè)”復(fù)選框,倒不如配置一下數(shù)據(jù)源控件,使它只檢索某一頁(yè)需要顯示的部分?jǐn)?shù)據(jù)。這樣做的好處是,當(dāng)顯示***頁(yè)的數(shù)據(jù)時(shí),你可以寫(xiě)一段只檢索產(chǎn)品1到產(chǎn)品10的sql語(yǔ)句,而不是把所有150條記錄全取出來(lái)。不過(guò),你的sql語(yǔ)句需要“聰明”的知道怎么把需要的數(shù)據(jù)從150條記錄里剪切出來(lái)。
自定義分頁(yè)的性能優(yōu)勢(shì)
我們可以從數(shù)據(jù)記錄的檢索能看出自定義分頁(yè)比默認(rèn)分頁(yè)的性能好。在我們的例子里,假設(shè)有150條產(chǎn)品數(shù)據(jù),每頁(yè)顯示10條。如果用自定義分頁(yè),用戶挨個(gè)瀏覽這15頁(yè)的數(shù)據(jù),150條數(shù)據(jù)會(huì)分批檢索出來(lái);如果用自定義分頁(yè),不管哪頁(yè)150條數(shù)據(jù)都會(huì)被檢索出來(lái),導(dǎo)致全部的數(shù)據(jù)檢索量也許是15倍的15條,可能有2250條之多!
雖然自定義分頁(yè)的性能顯而易見(jiàn),但默認(rèn)分頁(yè)卻非常簡(jiǎn)單。所以當(dāng)數(shù)據(jù)量小并且數(shù)據(jù)庫(kù)服務(wù)器的負(fù)載也不太重時(shí),我推薦你使用默認(rèn)分頁(yè)。如果你有幾百、上千以至上萬(wàn)條數(shù)據(jù)需要分頁(yè)顯示時(shí),一定要使用自定義分頁(yè)。當(dāng)然,像分頁(yè)ASPFAQ.com的數(shù)據(jù)庫(kù),現(xiàn)在的數(shù)據(jù)量只有200條問(wèn)答,用默認(rèn)分頁(yè)足夠了。(當(dāng)然,如果你覺(jué)得數(shù)據(jù)量將來(lái)不會(huì)增長(zhǎng),比如可能會(huì)維持在75條左右,那你就使用默認(rèn)分頁(yè)。但如果將來(lái)數(shù)據(jù)會(huì)增長(zhǎng)到7500條而你卻使用了默認(rèn)分頁(yè),一定會(huì)有些客戶不高興的?。?/P>
利用SQL Server 2005高效地取回一頁(yè)數(shù)據(jù)
像早前4Guys 的一篇文章講述的那樣,利用Microsoft SQL Server 2005獲得行值中提到的, SQL Server 2005 引入了許多返回行值的關(guān)鍵詞。特別提到關(guān)鍵詞the ROW_NUMBER()可以返回一列遞增的行數(shù)。因此,我們可以使ROW_NUMBER()寫(xiě)一條像下面的sql查詢語(yǔ)句來(lái)獲得某頁(yè)的數(shù)據(jù):
SELECT ... |
這里的@startRowIndex表示開(kāi)始行的索引,@maximumRows表示每頁(yè)顯示的***條數(shù)。這條語(yǔ)句會(huì)返回ROW_NUMBER()從開(kāi)始的索引行數(shù)到加每頁(yè)***條數(shù)的行數(shù)一部分?jǐn)?shù)據(jù)。
為了使表示跟具體,我們來(lái)看下下面這個(gè)例子。假設(shè)我們有一個(gè)表Employees有5000條數(shù)據(jù)(公司很不錯(cuò)哦?。?,看下面的查詢語(yǔ)句:
SELECT RowNum, EmployeeID, LastName, FirstName |
返回的結(jié)果如下:
RowNum EmployeeID LastName FirstName |
注意即使EmployeeID字段中間可能有短缺或者可能不是從1開(kāi)始的,ROW_NUMBER()也會(huì)從第1條記錄開(kāi)始,并且穩(wěn)定增長(zhǎng)。因此,如果你想每頁(yè)顯示10條數(shù)據(jù),我們想看第3頁(yè)時(shí),我們知道我們需要的數(shù)據(jù)是第31條到第40條,我們可以用一個(gè)簡(jiǎn)單的WHERE查詢得到。
配置ObjectDataSource支持自定義分頁(yè)
正如前面提到的,一方面,SqlDataSource并沒(méi)有設(shè)計(jì)成能提供自定義分頁(yè);另一方面,ObjectDataSource被設(shè)計(jì)成了能支持這個(gè)方案。ObjectDataSource被設(shè)計(jì)成能從一個(gè)object讀取數(shù)據(jù)的數(shù)據(jù)源控件。這個(gè)object不管從哪、怎樣取出數(shù)據(jù),像從一個(gè)WEB 服務(wù)、一個(gè)數(shù)據(jù)庫(kù)、一個(gè)文件系統(tǒng),或一個(gè)XML文件……等等。ObjectDataSource并不關(guān)心,它就像在數(shù)據(jù)存儲(chǔ)者和數(shù)據(jù)需要者(像一個(gè)GridView控件)之間充當(dāng)了一個(gè)中介。(更多關(guān)于ObjectDataSource請(qǐng)看ObjectDataSource控件概述)
當(dāng)把一個(gè)WEB數(shù)據(jù)控件綁定到一個(gè)ObjectDataSource并設(shè)置為“允許分頁(yè)”時(shí),如果你沒(méi)有具體設(shè)置ObjectDataSource來(lái)支持自定義分頁(yè),會(huì)使用默認(rèn)方案來(lái)分頁(yè)。要使ObjectDataSource支持自定義分頁(yè),你需要使一個(gè)object來(lái)提供以下這些方法:
1.一個(gè)包含兩個(gè)整型參數(shù)的方法。***個(gè)整數(shù)表示檢索數(shù)據(jù)開(kāi)始的索引位置(起點(diǎn)),而第二個(gè)整數(shù)表示每頁(yè)顯示的***條數(shù)。這個(gè)方法在調(diào)用時(shí)會(huì)返回我們所請(qǐng)求的數(shù)據(jù),也就是從檢索開(kāi)始位置到往后每頁(yè)顯示***條數(shù)的準(zhǔn)確記錄,而不是把所有數(shù)據(jù)記錄都取出來(lái)。
2.一個(gè)返回全部分頁(yè)的數(shù)據(jù)記錄數(shù)量(整數(shù)類型)的方法。(這是WEB數(shù)據(jù)控件在在初始化時(shí)需要的,這樣就可以顯示出要分出的總頁(yè)數(shù)或者是是否還需要有下一頁(yè)。)
如果你要用到基礎(chǔ)的object類型,配置ObjectDataSource來(lái)實(shí)現(xiàn)分頁(yè)是很簡(jiǎn)單的,來(lái)看一下下面的這些ObjectDataSource的屬性:
把EnablePaging設(shè)置為True
把SelectMethod設(shè)置為提供開(kāi)始頁(yè)和開(kāi)始***行數(shù)的參數(shù)以檢索數(shù)據(jù)的方法
把StartRowIndexParameterName設(shè)置為你在SelectMethod中用到的表示檢索開(kāi)始位置的變量;如果你不設(shè)置這個(gè),它默認(rèn)為取startRowIndex的值
把MaximumRowsParameterName設(shè)置為你在SelectMethod用到的每頁(yè)***記錄數(shù),如果你不設(shè)置這個(gè),它默認(rèn)取maximumROws的值
把SelectCountMethod設(shè)置為返回分頁(yè)的所有記錄的總數(shù)的方法
就這么簡(jiǎn)單。如果你像這樣配置了,那么ObjectDataSource就使用自定義分頁(yè)了。當(dāng)然,難點(diǎn)是創(chuàng)建能正確的取出數(shù)據(jù)的基礎(chǔ)對(duì)象。但你一旦有了這個(gè)基礎(chǔ)對(duì)象,要實(shí)現(xiàn)自定義分頁(yè)也就配置一下ObjectDataSource的幾個(gè)屬性而已。
創(chuàng)建一個(gè)支持自定義分頁(yè)的對(duì)象
為了使一個(gè)ObjectDataSource綁定到一個(gè)GridView,我們需要一個(gè)能靈活取得需要數(shù)據(jù)的底層對(duì)象,這個(gè)對(duì)象還能夠返回需要分頁(yè)的記錄數(shù)。像約瑟夫校長(zhǎng)的文章中寫(xiě)的那樣, 《在Visual Studio 2005和中ASP.NET 2.0使用強(qiáng)對(duì)象類型》和布萊恩·諾伊斯的文章 《利用Visual studio 2005 數(shù)據(jù)集設(shè)計(jì)器做數(shù)據(jù)訪問(wèn)層》中講述的那樣,在Visual studio 2005中創(chuàng)建能綁定到ObjectDataSource上的對(duì)象集是一件很容易的事情。首先要定義取出數(shù)據(jù)集的存儲(chǔ)過(guò)程(或sql查詢語(yǔ)句),以便讓那些強(qiáng)類型的數(shù)據(jù)集返回?cái)?shù)據(jù)。
接下來(lái),在本文的***提供的,有一個(gè)包含5000條職員的范例數(shù)據(jù)庫(kù)(大批量增加記錄是很容易的)。這個(gè)數(shù)據(jù)庫(kù)包含了2個(gè)自定義分頁(yè)示例中要用到的3個(gè)存儲(chǔ)過(guò)程:
GetEmployeesSubset(@startRowIndex int, @maximumRows int) – 返回按EmployeeID排序后從@startRowIndex開(kāi)始的最多@maximumRows條記錄。
GetEmployeesRowCount – 返回Employees表里的記錄總數(shù)。
GetEmployeesSubsetSorted(@sortExpression nvarchar(50), @startRowIndex int, @maximumRows int) – 這個(gè)存儲(chǔ)過(guò)程返回一頁(yè)經(jīng)過(guò)指定條件排序的數(shù)據(jù)。也就是說(shuō)能返回比如支持薪水排序后的記錄。(GetEmployeesSubset只能返回按EmployeeID排序的記錄。)這個(gè)擴(kuò)展當(dāng)你需要使用自定義分頁(yè)中有自定義排序時(shí)會(huì)用到。
在這篇文章里我們不計(jì)劃講述關(guān)于自定義分頁(yè)中的自定義排序,盡管在這篇文章的下載中也提供了這樣的例子;可以通過(guò)《自定義分頁(yè)中的數(shù)據(jù)排序》看到如何建立自定義分頁(yè)并支持雙向排序……
創(chuàng)建完這些存儲(chǔ)過(guò)程以后,我在我的項(xiàng)目里加入一個(gè)強(qiáng)類型的數(shù)據(jù)集文件(Employees.xsd)。然后在里面加入三個(gè)與三個(gè)存儲(chǔ)過(guò)程相對(duì)應(yīng)的方法。***我加入了一個(gè)返回EmployeesTableAdapter對(duì)象的方法GetEmployeesSubset(startRowIndex,maximumRows)和一個(gè)可以設(shè)置到ObjectDataSource屬性上的方法GetEmployeesRowCount()。(關(guān)于創(chuàng)建強(qiáng)類型數(shù)據(jù)集的詳細(xì)步驟去看《在Visual Studio 2005和 ASP.NET中使用強(qiáng)類型數(shù)據(jù)集》和思考特·格思里的博客文章《在VS2005和ASP.NET 2.0中用強(qiáng)類型數(shù)據(jù)集創(chuàng)建數(shù)據(jù)訪問(wèn)層》。)
默認(rèn)分頁(yè)與自定義分頁(yè)性能比較
在本文***有關(guān)于默認(rèn)分頁(yè)與自定義分頁(yè)的性能比較的數(shù)據(jù)庫(kù)(包含一張5000條數(shù)據(jù)的表),我用SQL和ASP.NET找出性能的差距。(這些測(cè)試在我電腦上做是很不嚴(yán)謹(jǐn)?shù)?,因?yàn)槲业碾娔X還同時(shí)運(yùn)行著其他的程序,雖然結(jié)果不能被稱做證明,但我想自定義分頁(yè)的性能在比較中肯定是有優(yōu)勢(shì)的。)
SQL查詢結(jié)果
默認(rèn)分頁(yè)
(從Employees檢索所有記錄)
持續(xù)時(shí)間 (秒) 讀取數(shù)量
1.455 383
1.405 383
1.434 383
1.394 383
1.365 383
平均: 1.411 平均: 383
自定義分頁(yè)
(從Employees檢索一頁(yè)記錄)
持續(xù)時(shí)間 (秒) 讀取數(shù)量
0.003 29
0.000 29
0.000 29
0.003 29
0.003 29
平均: 0.002 平均: 29
ASP.NET測(cè)試結(jié)果
默認(rèn)分頁(yè)
(從Employees查詢所有記錄)
頁(yè)面載入時(shí)間(秒)
2.34136852588807
2.35772228034569
2.43368277253115
2.43237562315881
2.33167064529151
平均: 2.379363969
自定義分頁(yè)
(從Employees查詢一頁(yè)記錄)
頁(yè)面載入時(shí)間(秒)
0.0259611207569677
0.0280046765720224
0.0359054013848129
0.0295534767686955
0.0300096800012292
平均: 0.029886871
SqlDataSource緩存
(查詢所有記錄,但緩存它們)
頁(yè)面載入時(shí)間(秒)
2.39666633608461
0.0431529705591074
0.0443528437273452
0.0442313199023898
0.0491523364002967
平均: 0.515511161
正如你看到的,自定義分頁(yè)要比默認(rèn)分頁(yè)快2倍以上。在相同的數(shù)據(jù)量下,GetEmployeesSubset(@startRowIndex int,@maximumROws int)要比簡(jiǎn)單的從Employees表中SELECT查詢出所有記錄要快470倍。在相同的ASP.NET環(huán)境下,自定義分頁(yè)要比默認(rèn)分頁(yè)快120倍。.高負(fù)荷的工作量使兩者接近,也就是說(shuō)建立數(shù)據(jù)庫(kù)連接和分配任務(wù)都會(huì)降低性能。不管怎樣,在性能方面兩個(gè)數(shù)量級(jí)相差太大了。并且這種差距會(huì)隨著數(shù)據(jù)量的增加或服務(wù)器的性能不同使載入更加明顯。
當(dāng)緩存是空的時(shí),SqlDataSource方案消耗時(shí)間很長(zhǎng),因?yàn)樗仨毜綌?shù)據(jù)庫(kù)去取得所有數(shù)據(jù),緩存在服務(wù)器。而服務(wù)器有空閑資源時(shí)才重新載入(如果只有少量的資源,緩存的數(shù)據(jù)集就會(huì)被清除出內(nèi)存)并且不再載入。在數(shù)據(jù)被緩存后,雖然在性能上很接近自定義分頁(yè),但0.516秒的平均時(shí)間會(huì)隨著越來(lái)越多的緩存數(shù)據(jù)而接近0.05秒。
總結(jié)
ASP.NET 1.x中的DataGrid和2.0中的GridView有兩種分頁(yè)方案:默認(rèn)分頁(yè)與自定義分頁(yè)。默認(rèn)分頁(yè)很容易做到,但每次訪問(wèn)每個(gè)頁(yè)面都會(huì)訪問(wèn)數(shù)據(jù)庫(kù)并取出所有數(shù)據(jù)。而自定義分頁(yè)非常高效,靈活地取出該取出的那些數(shù)據(jù)。SQL SERVER 2005由于它的新特性ROW_NUMBER()能取出行數(shù)的能力,使取出某段數(shù)據(jù)簡(jiǎn)單了。
如果你做的WEB程序現(xiàn)在或?qū)?lái)有可能讓用戶翻頁(yè)查看龐大的數(shù)據(jù),那么你用自定義分頁(yè)是很合適的。
【編輯推薦】