規模の大きなフォルダーやリストの操作

最終更新日: 2015年3月9日

適用対象: SharePoint Foundation 2010

この記事の内容
項目数が多いリストに対して実行するクエリのしきい値を調整する
フォルダーおよびリストを操作する
複数のバージョンのリスト アイテムを削除する

フォルダーやリストの規模が大きくなると、それらを適切に操作してパフォーマンスを最適化するカスタム コードを設計する必要があります。そうしないと、アプリケーションの実行速度が低下し、サービスやページの読み込みでタイムアウトが生じます。規模の大きなフォルダーやリストを扱うときに関係する主な問題領域は次の 2 つです。

  • クエリのしきい値の調整。サイトが発展してクエリの返す項目数がクエリのしきい値を超えるようになると、その調整次第でコードの動作に思いもよらぬ変化が生じます。

  • 大規模なフォルダーやリストからのアイテムの効率的な取得。

この 2 つの問題に取り組むには、オブジェクト モデルとフォルダーまたはリストとの相互作用を理解することが必要です。

項目数が多いリストに対して実行するクエリのしきい値を調整する

Microsoft SharePoint Foundation 2010 と Microsoft SharePoint Server 2010 で適用されるクエリのしきい値の既定値は 5,000 項目です。クエリの結果セットに依存するカスタム コードは、この上限を超えると、期待どおりのパフォーマンスを発揮しなくなります。また、項目数が 5,000 を超えるリストに対してクエリを実行した場合、クエリの条件にインデックス付きでないフィールドが含まれていると、クエリはリスト内のすべての行をスキャンしなければならないので失敗します。以下の手順に従って、しきい値を確認して大きな値に変更し、場合によっては、オブジェクト モデルがしきい値を無視できるようにしてください。

しきい値を確認して大きくする、またはオブジェクト モデルにしきい値を無視させるには

  1. サーバーの全体管理サイトで、[アプリケーション構成の管理] の [Web アプリケーションの管理] をクリックします。

  2. [全般設定] をクリックし、[リソースの調整] をクリックします。

  3. しきい値を確認して更新するか、オブジェクト モデルがしきい値を無視できるようにします。

フォルダーおよびリストを操作する

大規模なフォルダーやリストのパフォーマンス問題を解決する以下の推奨事項は、Steve Peschka の「ホワイト ペーパー : Office SharePoint\uc1\u174 R 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 をキーとして、リストへの参照を取得するようにすること。

    SPWeb.Lists プロパティから SPList オブジェクを取得するには、リストの GUID を使用する方法と、表示名をインデクサーとして使用する方法があります。どんな場合も、SPWeb.Lists[strDisplayName] ではなく、SPWeb.Lists[GUID] と SPWeb.GetList(strURL) を利用した方がよいでしょう。GUID による方法が優れているのは、それが一意かつ不変で、データベースを一度だけ検索すれば済むからです。表示名インデクサーは、サイトのリスト名をすべて取得したうえで、それらを文字列として比較します。手元にリストの GUID がなくて URL がある場合は、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);  
}
}