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;用户在浏览已有的页面时,通过分页信息返 回指定的页的数据(此时效率最高);当用户在之前的页面中没找到可用信息时,则通过更多再一次获取从之前最后位置起的指定页数的所有数据,并得到所有分页 信息。如此循环,直至用户找到想要的信息。代码如下:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="QueryPager.aspx.cs" Inherits="QueryPager" enableSessionState="True"%>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Large List Paging</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <table>

            <tr>

                <td>

                    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false">

                        <Columns>

                            <asp:BoundField  DataField="ID" HeaderText="ID"/>

                            <asp:BoundField  DataField="Title" HeaderText="标题"/>

                        </Columns>

                    </asp:GridView>

                </td>

            </tr>

            <tr>

                <td valign="middle" align="center">

                    <asp:DropDownList ID="ddlPageNumber" runat="server" AutoPostBack="True">

                    </asp:DropDownList>

                </td>

            </tr>

        </table>

    </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using Microsoft.SharePoint.Utilities;

 

 

public partial class QueryPager : System.Web.UI.Page

{

    /// <summary>

    /// 每页显示的行数

    /// </summary>

    int iPageSize = 5;

    /// <summary>

    /// 每次显示的页数

    /// </summary>

    int iPages = 5;

    int iRowsCount = 0;

    int iPageCount = 0;

    SPSite site;

    SPWeb web;

    SPList list;

    SPQuery query;

    protected void Page_Load(object sender, EventArgs e)

    {

        site = new SPSite("http://MossSvr");

        web = site.AllWebs["news"];

        list = web.Lists["MyList"];

        query = new SPQuery();

        query.ViewAttributes = "Scope='Recursive'";

 

        if (Page.IsPostBack)

        {

            //提交时

            if (ddlPageNumber.SelectedItem.Text == "more")

            {

                //显示更多

                GetResults(iPageSize * iPages, true, ddlPageNumber.SelectedItem.Value);

            }

            else

            {

                GetResults(iPageSize, false, ddlPageNumber.SelectedItem.Value);

            }

        }

        else

        {

            //首次加载时

            //将返回的行数设置为每次显示页数的最多

            GetResults(iPageSize * iPages, true, string.Empty);

        }

        //Response.Write(SPEncode.HtmlEncode(""));

        //Finalize

        web.Dispose();

        site.Dispose();

 

    }

    private void GetResults(int iRowLimit,Boolean bMore,string sPageInfo)

    {

        query.RowLimit = (uint)iRowLimit;

        //定位

        if (sPageInfo.Length > 0)

        {

            query.ListItemCollectionPosition = new SPListItemCollectionPosition(sPageInfo);

        }

        SPListItemCollection listitems = list.GetItems(query);

        iRowsCount = listitems.Count;

        DataTable dtResults = listitems.GetDataTable();

        if (bMore)

        {

            iPageCount = (int)Math.Ceiling(iRowsCount / (decimal)iPageSize);

            int ddlListItemCount = ddlPageNumber.Items.Count;

            if (ddlListItemCount > 0)

            {

                //删除More

                ddlListItemCount--;

                ddlPageNumber.Items.Remove(ddlPageNumber.Items[ddlListItemCount]);

            }

            if (iRowsCount > 0)

            {

                int iRowNum = 0;

                SPListItem listitem = null;

               

                //新增的第一页

                ddlPageNumber.Items.Add(new ListItem(Convert.ToString(ddlListItemCount + 1), sPageInfo));

                for (int i = 2; i <= iPageCount; i++)

                {

                    //""

                    iRowNum = (i-1) * iPageSize - 1;

                    listitem = listitems[iRowNum];

                    sPageInfo = "Paged=TRUE&p_FSObjType=" + listitem["FSObjType"].ToString() + "&p_Title=" + listitem["Title"].ToString() + "&p_ID=" + listitem["ID"].ToString();

                    ddlPageNumber.Items.Add(new ListItem(Convert.ToString(ddlListItemCount + i), sPageInfo));

                }

                //显示更多...

                if (iPageCount == iPages && listitems.ListItemCollectionPosition != null)

                {

                    ddlPageNumber.Items.Add(new ListItem("more", listitems.ListItemCollectionPosition.PagingInfo));

                }

           

                //移除多余的行...

                if (iRowsCount > iPageSize)

                {

                    for (int i = iRowsCount - 1; i >= iPageSize; i--)

                    {

                        dtResults.Rows.RemoveAt(i);

                    }

                }

            }

        }

        GridView1.DataSource = dtResults;

        GridView1.DataBind();

    }

}

    缺点在于,由于第一页是最常使用的页面,但却需要获取可能不需要的后面页的数据以得到后面页的分页信息,这可能导致性能的下降。解决办法是降低每页显示的数据以及一次要显示的页数。
Posted on 2007-11-19 12:37  dotnba  阅读(2908)  评论(1编辑  收藏  举报