处理大型文件夹和列表

上次修改时间: 2015年3月9日

适用范围: SharePoint Foundation 2010

本文内容
大列表查询的限制
处理文件夹和列表
删除列表项的多个版本

当文件夹和列表的大小增加时,您必须设计处理它们的自定义代码以优化性能。否则,您的应用程序将会运行缓慢,并可能引起服务或页面加载超时。处理大文件夹和列表时主要需要关注以下两个方面:

  • 查询限制,随着时间的推移,当您的网站不断发展并且您的查询开始返回超过查询阈值的项目时,这会引发您的代码行为发生不可预测的意外更改。

  • 从大文件夹和列表中高效检索项目。

为了解决这两个问题,您必须了解对象模型是如何与文件夹和列表交互的。

大列表查询的限制

Microsoft SharePoint Foundation 2010 和 Microsoft SharePoint Server 2010 应用 5,000 个项目的默认查询阈值。任何依赖可能会超过此最大值的查询结果集的自定义代码都不会按预期执行。如果列表中包含 5,000 多个项目并且这些项目包含未编入查询条件索引的字段,那么对这些列表的查询也将失败,因为这些查询必须扫描列表中的所有行。可按照下面列出的步骤查看并增加此限制或允许对象模型替代此限制:

查看并增加此阈值或允许对象模型替代此阈值

  1. 在"管理中心"网站上的"应用程序管理"下,单击"管理 Web 应用程序"。

  2. 单击"常规设置",然后单击"资源限制"。

  3. 查看并更新阈值或允许对象模型替代限制。

处理文件夹和列表

用于解决在处理大文件夹和列表时的性能问题的以下建议基于 Steve Peschka 的白皮书在 Office SharePoint Server 2007 中处理大型列表(该链接可能指向英文页面)中报告的测试结果。这些建议也适用于 Microsoft SharePoint Server 2010。有关使用 SPQuery 和 PortalSiteMapProvider 类(它专用于 SharePoint Server 2010)的其他指导,请参阅在 SharePoint Server 中编写有效代码

在处理文件夹和列表时:

  • 不要使用 SPList.Items。

    SPList.Items 会选择所有子文件夹中的所有项目,包括列表中的所有字段。为每个用例使用以下替代项。

    • 添加项目

      不要调用 SPList.Items.Add,而是使用 SPList.AddItem。

    • 检索列表中的所有项目

      不要使用 SPList.Items,而是使用 SPList.GetItems(SPQuery query) 。根据需要应用筛选器,并只指定所需字段以使查询更高效。如果列表包含的项目超过 2,000 个,请按不超过 2,000 个项目的增量对列表进行分页。下面的代码示例演示如何对大型列表进行分页。

      好的编码实践

      使用 SPList.GetItems 检索项目

      SPQuery query = new SPQuery();
      SPListItemCollection spListItems ; 
      string lastItemIdOnPage = null; // Page position.
      int itemCount = 2000
       
      while (itemCount == 2000)
      {
          // Include only the fields you will use.
          query.ViewFields = "<FieldRef Name=\"ID\"/><FieldRef Name=\"ContentTypeId\"/>";   
          query.RowLimit = 2000; // Only select the top 2000.
          // Include items in a subfolder (if necessary).
          query.ViewAttributes = "Scope=\"Recursive\"";
          StringBuilder sb = new StringBuilder();
          // To make the query order by ID and stop scanning the table, specify the OrderBy override attribute.
          sb.Append("<OrderBy Override=\"TRUE\"><FieldRef Name=\"ID\"/></OrderBy>");
          //.. Append more text as necessary ..
          query.Query = sb.ToString();
          // Get 2,000 more items.
       
          SPListItemCollectionPosition pos = new SPListItemCollectionPosition(lastItemIdOnPage);
          query.ListItemCollectionPosition = pos; //Page info.
          spListItems = spList.GetItems(query);
          lastItemIdOnPage = spListItems.ListItemCollectionPosition.PagingInfo;
          // Code to enumerate the spListItems.
          // If itemCount <2000, finish the enumeration.
          itemCount = spListItems.Count;
      
      }
      
      Dim query As New SPQuery()
      Dim spListItems As SPListItemCollection
      Dim lastItemIdOnPage As String = Nothing ' Page position.
      Dim itemCount As Integer = 2000
      
      Do While itemCount = 2000
          ' Include only the fields you will use.
          query.ViewFields = "<FieldRef Name=""ID""/><FieldRef Name=""ContentTypeId""/>"
          query.RowLimit = 2000 ' Only select the top 2000.
          ' Include items in a subfolder (if necessary).
          query.ViewAttributes = "Scope=""Recursive"""
          Dim sb As New StringBuilder()
          ' To make the query order by ID and stop scanning the table, specify the OrderBy override attribute.
          sb.Append("<OrderBy Override=""TRUE""><FieldRef Name=""ID""/></OrderBy>")
          '.. Append more text as necessary ..
          query.Query = sb.ToString()
          ' Get 2,000 more items.
      
          Dim pos As New SPListItemCollectionPosition(lastItemIdOnPage)
          query.ListItemCollectionPosition = pos 'Page info.
          spListItems = spList.GetItems(query)
          lastItemIdOnPage = spListItems.ListItemCollectionPosition.PagingInfo
          ' Code to enumerate the spListItems.
          ' If itemCount <2000, finish the enumeration.
          itemCount = spListItems.Count
      Loop
      

      下面的示例演示如何枚举大型列表并对其进行分页。

      SPWeb oWebsite = SPContext.Current.Web;
      SPList oList = oWebsite.Lists["Announcements"];
      
      SPQuery oQuery = new SPQuery();
      oQuery.RowLimit = 10;
      int intIndex = 1;
      
      do
      {
          Response.Write("<BR>Page: " + intIndex + "<BR>");
          SPListItemCollection collListItems = oList.GetItems(oQuery);
      
          foreach (SPListItem oListItem in collListItems)
          {
              Response.Write(SPEncode.HtmlEncode(oListItem["Title"].ToString()) +"<BR>");
          }
      
          oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition;
          intIndex++;
      } while (oQuery.ListItemCollectionPosition != null);
      
       Dim oWebsite As SPWeb = SPContext.Current.Web
      Dim oList As SPList = oWebsite.Lists("Announcements")
      
      Dim oQuery As New SPQuery()
      oQuery.RowLimit = 10
      Dim intIndex As Integer = 1
      
      Do
          Response.Write("<BR>Page: " & intIndex & "<BR>")
          Dim collListItems As SPListItemCollection = oList.GetItems(oQuery)
      
          For Each oListItem As SPListItem In collListItems
              Response.Write(SPEncode.HtmlEncode(oListItem("Title").ToString()) & "<BR>")
          Next oListItem
      
          oQuery.ListItemCollectionPosition = collListItems.ListItemCollectionPosition
          intIndex += 1
      Loop While oQuery.ListItemCollectionPosition IsNot Nothing
      
    • 按标识符获取项目

      不要使用 SPList.Items.GetItemById,而是使用 SPList.GetItemById(int id, string field1, params string[] fields)。指定项目标识符和所需字段。

  • 不要枚举整个 SPList.Items 集合或 SPFolder.Files 集合。

    表 1 中的左边一列列出了一些方法和属性,如果使用这些方法和属性,将会枚举整个 SPList.Items 集合并导致大型列表性能低下和存在限制。请改用右边一列中列出的性能较好的替代项。

    表 1. 枚举 SPList.Items 的替代项

    性能较差的方法和属性

    性能较好的替代项

    SPList.Items.Count

    SPList.ItemCount

    SPList.Items.XmlDataSchema

    创建一个 SPQuery 对象以便只检索所需项目。

    SPList.Items.NumberOfFields

    创建一个 SPQuery 对象(指定 ViewFields)以便只检索所需项目。

    SPList.Items[System.Guid]

    SPList.GetItemByUniqueId(System.Guid)

    SPList.Items[System.Int32]

    SPList.GetItemById(System.Int32)

    SPList.Items.GetItemById(System.Int32)

    SPList.GetItemById(System.Int32)

    SPList.Items.ReorderItems(System.Boolean[],System.Int32[],System.Int32)

    使用 SPQuery 执行分页查询并在每页中对项目重新排序。

    SPList.Items.ListItemCollectionPosition

    ContentIterator.ProcessListItems(SPList, ContentIterator.ItemProcessor, ContentIterator.ItemProcessorErrorCallout)(仅限 Microsoft SharePoint Server 2010)

    SPList.Items.ListItemCollectionPosition

    ContentIterator.ProcessListItems(SPList, ContentIterator.ItemProcessor, ContentIterator.ItemProcessorErrorCallout)(仅限 SharePoint Server 2010)

    备注

    使用 SPList.ItemCount 属性是检索列表中的项目数的推荐方法。但是,由于为了性能而优化此属性会产生副作用,因此该属性偶尔会返回意外结果。例如,如果您需要精确的项目数,则应使用性能较差的 GetItems(SPQuery query),如前面的代码示例所示。

  • 尽可能使用列表的 GUID 或 URL 作为键来获取对列表的引用。

    可以使用列表的 GUID 或显示名称作为索引器从 SPWeb.Lists 属性中检索 SPList 对象。使用 SPWeb.Lists[GUID] 和 SPWeb.GetList(strURL) 总是比使用 SPWeb.Lists[strDisplayName] 更可取。最好使用 GUID,因为它是唯一的、永久的,并且只需要单次数据库查找。显示名称索引器检索网站中所有列表的名称,然后对它们执行字符串比较。如果您有列表 URL 而没有 GUID,则可以使用 SPWeb 中的 GetList 方法在内容数据库中查找列表的 GUID,然后再检索列表。

  • 不要枚举整个 SPFolder.Files 集合。

    表 2 中的左边一列列出了使 SPFolder.Files 集合膨胀并导致大型列表性能低下和存在限制的方法和属性。请改用右边一列中性能较好的替代项。

    表 2. SPFolders.Files 的替代项

    性能较差的方法和属性

    性能较好的替代项

    SPFolder.Files.Count

    SPFolder.ItemCount

    SPFolder.Files.GetEnumerator()

    ContentIterator.ProcessFilesInFolder(SPFolder, System.Boolean, ContentIterator.FileProcessor, ContentIterator.FileProcessorErrorCallout)(仅限 SharePoint Server 2010)

    SPFolder.Files[System.String]

    ContentIterator.GetFileInFolder(SPFolder, System.String) 或者 SPFolder.ParentWeb.GetFile(SPUrlUtility.CombineUrl(SPFolder.Url, System.String)(仅限 SharePoint Server 2010)

    SPFolder.Files[System.Int32]

    不要使用。切换为 ContentIterator.ProcessFilesInFolder 并在迭代过程中统计项数。(仅限 SharePoint Server 2010)

删除列表项的多个版本

删除列表项的多个版本时,请使用 DeleteByID() 该方法;不要使用 Delete() 方法。如果从 SPListItemVersionCollection 对象中删除每个 SPListItemVersion 对象,则会遇到性能问题。建议创建一个包含每个版本的 ID 属性的数组,然后使用 SPFileVersionCollection.DeleteByID 方法删除每个版本。下面的代码示例演示在删除自定义列表的第一项的所有版本时不建议和建议采用的方法。

不好的编码实践

删除每个 SPListItemVersion 对象

SPSite site = new SPSite("site url");
SPWeb web = site.OpenWeb();
SPList list = web.Lists["custom list name"];
SPListItem item = list.GetItemById(1); 
SPListItemVersionCollection vCollection = item.Versions;
ArrayList idList = new ArrayList();
foreach(SPListItemVersion ver in vCollection)
{
  idList.Add(ver.VersionId);
}
foreach(int verID in idList)
{
  SPListItemVersion version = vCollection.GetVersionFromID(verID); 
try
{
  version.Delete();
}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);  
}
}

好的编码实践

使用 SPFileVersionCollection.DeleteByID 方法删除列表项的每个版本

SPSite site = new SPSite("site url");
SPWeb web = site.OpenWeb();
SPList list = web.Lists["custom list name"];
SPListItem item = list.GetItemById(1);
SPFile file = web.GetFile(item.Url);
SPFileVersionCollection collection = file.Versions;
ArrayList idList = new ArrayList();
foreach (SPFileVersion ver in collection)
{
  idList.Add(ver.ID);
}
foreach (int verID in idList)
{
try
{
  collection.DeleteByID(verID);
}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);  
}
}

如果要删除文档库中项目的版本,可通过检索 SPListItem.File.Versions 属性来使用类似方法,如下面的代码示例所示。

好的编码实践

使用 SPFileVersionCollection.DeleteByID 方法删除文档库中列表项的每个版本

SPSite site = new SPSite("site url");
SPWeb web = site.OpenWeb();
SPList list = web.Lists["custom list name"];

SPFile file = list.RootFolder.Files[0];
SPFileVersionCollection collection = file.Versions;
ArrayList idList = new ArrayList();
foreach (SPFileVersion ver in collection)
{
  idList.Add(ver.ID);
}
foreach (int verID in idList)
{
try
{
  collection.DeleteByID(verID);
}
catch (Exception ex)
{
  MessageBox.Show(ex.Message);  
}
}