在WSS/MOSS中使用SPQuery分頁
2007-11-19 11:52:19 原文地址: http://blog.sina.com.cn/u/3f2ef11801000bw1 [查看原文] Steve Peschka在技術白皮書《White Paper: Working with large lists in Office SharePoint Server 2007》一文中,介紹了利用WSS/MOSS物件模型進行資料訪問的多種方法。文章的重點是在大列表(Large List,即列表中包含的清單項目的數量巨大,十萬條以上)下如何選擇合適的資料存取方法提高對資料操作的效能。
文中的測試大都是通過使用RowLimt屬性限制返回資料條目進行的。本質上是返回第一頁或者首頁。從生產或者實際應用角度來看,並不具備可操作性。當然,從測試的角度而言已經基本達到目標。
對於大資料量的訪問,無論何時本身都是令人頭疼的問題。通常的辦法都是只返回需要的資料,以減少資料讀取和網路傳輸的時間。一般會是在資料庫層通過預存程序來解決。Steve Peschka在白皮書中並沒有介紹如何進行分頁。簡單地,如果我們使用GetItems將資料全部返回到一個SPListItemCollection或者再進一步GetDataTable到一個DataTable中,然後再進行分頁。這樣減少的僅僅是Render到頁面的時間以及頁面展示的時間,並不能真正減少對資料庫訪問的時間以及資料從資料庫傳輸到應用伺服器的時間。那麼我們應如何來有效地進行對大列表的資料訪問呢?一種想法是如同以前,直接對資料庫訪問。但是這種方式看起來容易,實現起來難度卻非常大。因為微軟並沒有公開資料庫層的設計。如果再包括許可權等的控制,幾乎是重寫一套資料存取方法。第二種就是利用微軟現有的物件模型(OM)進行資料訪問。這種方式是利用現有的介面,通過合適的方式進行資料訪問。
SPQuery是無論在WSS還是MOSS中都可以使用的一個資料存取方法。同時,在該白皮書中的表現也中規中钜,並且可以獲得即時資料,不失為資料訪問的一個通用方法。SPQuery有三個屬性值得關註:ViewAttributes、RowLimit和ListItemCollectionPosition。通過ViewAttributes可以設定檢索的列表的範圍(Scope),是否包含子檔案(Default、Recursive、RecursiveAll和FilesOnly)。而後兩個屬性則和分頁直接相關。
先介紹測試環境,在一台名為MossSvr的MOSS伺服器上安裝有MOSS2007,同時有一個叫做News的子網站,並在其上建立了測試清單MyList。在MyList中建立檔案夾subfolder1和subfolder2,並直接包含了標題為ListItem1、ListItem2和ListItem3的三個清單項目。subfolder1下直接包含了標題為ListItem11到標題為ListItem14的四個清單項目,subfolder2下直接包含了標題為ListItem21到標題為ListItem25的五個清單項目。因為本文主要介紹分頁方法,所以並不需要太多的測試資料。
根據環境,我們先來改一段WSS3.0 SDK中的一段代碼:
site = new SPSite("http://MossSvr");
web = site.AllWebs["news"];
list = web.Lists["MyList"];
query = new SPQuery();
//檢索所有的項目
query.ViewAttributes = "Scope='RecursiveAll'";
query.ViewFields = "<FieldRef Name='ID'/><FieldRef Name='FSObjType'/><FieldRef Name='Title'/>";
//使用查詢和排序(分頁中通常會遇到的)
query.Query = "<Where><Eq><FieldRef Name='ContentType'/><Value Type='Text'> 項目</Value></Eq></Where><OrderBy><FieldRef Name=/"Title/" Ascending=/"false/"/></OrderBy>" ;
int i = 1;
query.RowLimit = (uint)iPageSize;
do
{
SPListItemCollection listItems = list.GetItems(query);
Response.Write("第" + i.ToString() + "頁<br/>");
//Response.Write(SPlicp.PagingInfo + "<br/>");
foreach (SPListItem listItem in listItems)
{
Response.Write(listItem["ID"].ToString() + "." + SPEncode.HtmlEncode(listItem["Title"].ToString()) + "<BR/>");
}
query.ListItemCollectionPosition = listItems.ListItemCollectionPosition;
try
{
Response.Write(listItems.ListItemCollectionPosition.PagingInfo + "<br/>");
}
catch
{
}
i++;
}
while (query.ListItemCollectionPosition != null);
//Finalize
web.Dispose();
site.Dispose();
我們看輸出的結果:
第1頁
5.ListItem3
14.ListItem25
13.ListItem24
12.ListItem23
11.ListItem22
Paged=TRUE&p_FSObjType=0&p_Title=ListItem22&p_ID=11
第2頁
10.ListItem21
4.ListItem2
9.ListItem14
8.ListItem13
7.ListItem12
Paged=TRUE&p_FSObjType=0&p_Title=ListItem12&p_ID=7
第3頁
6.ListItem11
1.ListItem1
我們可以看到,這個結果已經排序,並且實現了分頁。其中PagingInfo的資訊非常重要,它是一個類似QueryString構造方式的一個字串。Paged表示是否分頁。後面有三個屬性:p_FSObjType、p_Title、p_ID。其實是列表的三個列。經我測試,這三個列對分頁至關重要,估計微軟是通過這個來定位一個清單項目的位置。
但是我們仔細看代碼就會發現,用這段代碼來實現分頁還是不夠的。因為首先必需知道上一個PagingInfo,我們才可以得到下一頁的內容。並且,無論是 SPQuery類還是ListItemCollectionPosition類都沒有關於整個查詢返回的行數的資訊,因此無法得到總共有多少頁或者行。所以,我們必需變通的方式來解決。
從統計學的角度講,使用者一般比較關注前面的查詢結果,越到後面的頁查看的機會越小。因此,我們只需要先返回前面的頁即可。按照每頁顯示20-50行,一次最多顯示10頁,那麼返回的資料量大概在5000條以內。這個基本是可接受的範圍。因此,我們可以先獲得前10頁的資料,然後再進行分頁。至於要獲得後面的分頁,則可以在查看更多資訊時獲得。
根據這個考慮,我們就可以實現分頁:第一次擷取指定頁數的所有資料,並得到各個頁面分頁資訊即PageInfo;使用者在瀏覽已有的頁面時,通過分頁資訊返回指定的頁的資料(此時效率最高);當使用者在之前的頁面中沒找到可用資訊時,則通過更多再一次擷取從之前最後位置起的指定頁數的所有資料,並得到所有分頁資訊。如此迴圈,直至使用者找到想要的資訊。分頁範例程式碼參看:http://www.cnblogs.com/dotnba/archive/2007/11/19/964004.html
缺點在於,由於第一頁是最常使用的頁面,但卻需要擷取可能不需要的後面頁的資料以得到後面頁的分頁資訊,這可能導致效能的下降。解決辦法是降低每頁顯示的資料以及一次要顯示的頁數。
轉自:http://q.blog.sina.com.cn/blogfile.php?id=1000267941&fid=3f2ef11801000bw1