Exchange で EWS を使用してページング検索を実行する

Exchange を対象とする EWS マネージ API または EWS のアプリケーションでページングされた検索を実行する方法を説明します。

ページングは、検索結果のサイズを制御するための EWS の機能です。 1 度の EWS の応答で検索セット全体を取得する代わりに、複数の EWS 応答で小さなセットに分けて取得できます。 たとえば、受信トレイに 10,000 件のメール メッセージがあるユーザーについて検討します。 仮に、10,000 件のメールすべてを 1 度の大規模な応答で取得できる場合でも、帯域幅またはパフォーマンス上の理由でより管理しやすいチャンクに分割することを望まれるかもしれません。 ページングは、そうするのに最適なツールを提供します。

注:

1 つの応答で 10,000 のアイテムを取得できると仮定していますが、実際には EWS 調整のため行えません。 詳細については、「Exchange での EWS 調整」を参照してください。

表 1. EWS マネージ API と EWS のページング パラメーター

構成または取得するもの EWS マネージ API で使用するもの EWS で使用するもの
応答中のアイテムまたはフォルダーの最大数
ItemView コンストラクター または FolderView コンストラクターpageSize パラメーター
または
PagedView.PageSize プロパティ
IndexedPageItemView 要素または IndexedPageFolderView 要素の MaxEntriesReturned 属性
アイテムまたはフォルダーのリストの開始点
ItemView コンストラクターまたは FolderView コンストラクターの offsetBasePoint パラメーター
または
PagedView.OffsetBasePoint プロパティ
IndexedPageItemView 要素または IndexedPageFolderView 要素の BasePoint 属性
開始点からのオフセット
ItemView コンストラクターまたは FolderView コンストラクターの offset パラメーター
または
PagedView.Offset プロパティ
IndexedPageItemView 要素または IndexedPageFolderView 要素の Offset 属性
サーバー上の結果の総数
FindItemsResults.TotalCount プロパティまたは FindFoldersResults.TotalCount プロパティ
RootFolder (FindItemResponseMessage) 要素または RootFolder (FindFolderResponseMessage) 要素の TotalItemsInView 属性
現在の応答に含まれない次のアイテムまたはフォルダーのオフセット現在の応答に含まれない次のアイテムまたはフォルダーのオフセット
FindItemsResults.NextPageOffset プロパティまたは FindFoldersResults.NextPageOffset プロパティ
RootFolder 要素の IndexedPagingOffset 属性
応答が最後のアイテムまたはフォルダーをリストに含んでいるかどうかを示すインジケーター
FindItemsResults.MoreAvailable プロパティまたは FindFoldersResults.MoreAvailable プロパティ
RootFolder 要素の IncludesLastItemInRange 属性

ページングのしくみ

ページングのしくみを理解するには、フォルダー内のメッセージを屋外に並べられた広告版に置き換えて考えると分かりやすくなります。 これらの広告版のいくつかを魔法のウィンドウを通して見ることができます。 ウィンドウのサイズを変更 (同時に表示する広告版の数を増やすまたは減らすため) したり、ウィンドウを移動 (表示する広告版を制御するため) したりできます。 このウィンドウの操作がページングです。

要求を Exchange サーバーに送信する際、返すアイテム数を指定するためにウィンドウのサイズを指定します。 開始点 (行の先頭または末尾) とその開始点からのオフセット (アイテム数で表す) を指定して、ウィンドウの位置を設定します。 ウィンドウの先頭は、開始点からのオフセットで指定されたアイテムの数です。

ページングの魅力は、サーバーの応答と、その応答をアプリケーションで使用して次の要求を形成する際に発揮されます。 次の要求に備えて "ウィンドウ" を構成する方法を決定するのに使用できる次の 3 つの情報がサーバーから提供されます。

  • 応答に含まれている結果に、サーバー上の結果セット全体の最後のアイテムが含まれているかどうか。

  • サーバー上の結果セットのアイテムの合計数。

  • 現在の応答に含まれていない、結果セット内の次のアイテムにウィンドウを移動する場合に指定すべきオフセットの値。

簡単な例を見てみましょう。 受信トレイに 15 件のメッセージがあるとします。 アプリケーションによって、メッセージのリストの初めから最大 10 個のアイテム (オフセットは 0 になります) を取得するための最初の要求が送信されます。 サーバーからの応答には最初の 10 件のメッセージが含まれ、応答には最後のアイテムが含まれておらず、合計 15 個のアイテムがあり、次のオフセットを 10 にする必要があることが示されます。

図 1. 15 アイテムのリストの初めからオフセット 0 で 10 アイテムを要求

15 アイテムのリストの初めからオフセット 0 で 10 アイテムを要求した結果を表示している図。

その後、アプリケーションは同じ要求をサーバーに再送信します。唯一の変更は、オフセットが 10 になりました。 サーバーは、最後の 5 つの項目を返し、応答に最後の項目が含まれている、合計 15 個の項目があること、次のオフセットが 15 であることを示します (もちろん、最後に達したので、次のオフセットはありません)。

図 2. 15 アイテムのリストの初めからオフセット 10 で 10 アイテムを要求

15 アイテムのリストの初めからオフセット 10 で 10 アイテムを要求した結果を表示している図。

ページングのデザイン上の考慮事項

アプリケーションでページングを最大限に活用するには、いくつかの考慮事項が必要です。 たとえば、"ウィンドウ" の大きさはどのくらいですか? "ウィンドウ" を移動しているときにサーバーの結果が変わるとどうなりますか?

ウィンドウのサイズの決定

すべてのアプリケーションが使用するべき、あらゆる状況に適したエントリの最大数といったものはありません。 アプリケーションにとって適切な数は、いくつかの異なる要素に基づいて決定されます。 しかし、次のガイドラインに従うと便利です。

  • 既定で、Exchange では 1 つの要求に返すことのできる最大アイテム数を 1000 に限定しています。

  • エントリの最大数を大きい数字に設定すると、すべてのアイテムを取得するのに送信する要求の数は少なくなりますが、応答の待機時間が長くなります。

  • エントリの最大数を小さい数に設定すると、応答時間が短くなりますが、すべてのアイテムを取得するのに送信する要求の数が増えます。

結果セットが変更された場合の処理

この記事の前の簡単な例では、ユーザーの受信トレイ内のアイテムの数は一定のままでした。 ただし、実際には、受信トレイ内のアイテムの数は頻繁に変更される可能性があります。 新しいメッセージは受信でき、アイテムはいつでも削除または移動できます。 しかし、これはページングにどのような影響を与えますか? 前のシナリオ例を変更して調べてみましょう。

再度、ユーザーの受信トレイには 15 アイテムある設定で、同じ最初の要求を送信します。 先の例同様、サーバーからの応答として最初の 10 件のメッセージが含まれ、応答に最後のアイテムが含まれておらず、合計 15 個のアイテムがあり、次のオフセットを 10 にする必要があることが示されます (図 1)。

アプリケーションでそれら 10 のアイテムが処理されている間、新しいメッセージが受信トレイに到着し、サーバーの結果セットに追加されます。 アプリケーションが同じ要求をサーバーに再送信します (オフセットが 10 に設定されている)。 今回の場合、サーバーは 6 アイテムを返し、合計 16 アイテムが結果セットにあることが示されます。

この時点で、これが問題かどうか認識できないかもしれません。 結局のところ、2 つの応答を通して 16 アイテムが返されただけのことです。 答えは、新しいアイテムがリストのどこに配置されたかによって異なります。 最も古いアイテム (受信した日時順) が先頭に来るようにリストが並べ替えられている場合、このシナリオでは心配する要因となるものはありません。 新しいアイテムはリストの末尾に配置され、2 番目の応答に含まれます。

図 3. 16 アイテムのリストの初めからオフセット 10 で 10 アイテムを要求 (16 番目のアイテムが新規アイテムとしてリストに含まれる)

16 番目のアイテムがリストの終わりに追加されたときに、16 アイテムのリストの最初からオフセット 10 で 10 アイテムを要求した結果を表示している図。

最新のアイテムが最初になるように、リストが並べ替えられている場合は、別の話です。 この例では、2 番目の要求の最初のアイテムがその前の要求の最後のアイテムになり、残りの 5 アイテムは元の 15 アイテムのものです。 仮想的な魔法のウィンドウという観点で考えると、ウィンドウの位置は 10 移動しましたが、広告版自体も 1 移動したことになります。

図 4. 16 アイテムのリストの初めからオフセット 10 で 10 アイテムを要求 (最初のアイテムが新規アイテムとしてリストに含まれる)

16 番目のアイテムがリストの初めに追加されたときに、16 アイテムのリストの最初からオフセット 10 で 10 アイテムを要求した結果を表示している図。

サーバー上の結果の変更を検出する 1 つの方法は、アンカー項目の概念を使用することです。 アンカー項目は、応答の追加項目であり、残りの結果と共に処理されませんが、項目自体がシフトしているかどうかを確認するために、次の結果と比較するために使用されます。 簡単な例でもう一度ビルドすると、アプリケーションで "ウィンドウ" サイズが 10 を使用している場合は、実際に返す項目の最大数を 11 に設定します。 アプリケーションは、応答の最初の 10 項目を通常どおりに処理します。 最後の項目の場合は、項目の識別子をアンカーとして保存し、次の要求を 10 のオフセットで発行します。 データが変更されていない場合、2 番目の応答の最初の項目には、アンカーと一致する項目識別子が必要です。 項目識別子が一致しない場合は、データが削除されているか、既に "ページング" されているリストの部分に挿入されていることがわかります。

データが変更されたことが分かっても、対処する方法を決定する必要は依然として残ります。 この質問に対しても、あらゆる状況に適した答えはありません。 アプリケーションの性質と、すべてのアイテムを取得することの重要度によって、アクションは異なります。 すべて無視し最初からプロセスをやり直すか、変更の発生場所を追跡して特定することもできます。

例:EWS マネージ API を使用して、ページング検索を実行する

ページングは、次の EWS マネージ API メソッドによってサポートされています。

EWS マネージ API を使用している場合、アプリケーションでは ItemView クラスまたは FolderView クラスでページングを構成し、FindItemsResults クラスまたは FindFoldersResults クラスでページングに関する情報がサーバーから送られます。

次の例では、各応答で 5 アイテムを返すページング検索を使用してフォルダー内のすべてのアイテムを取得します。 さらに、サーバー上の結果の変更を検出するためにアンカーとして機能する追加のアイテムを取得します。

この例では、ExchangeService オブジェクトは Credentials プロパティと Url プロパティの有効な値で初期化されているものとします。

using Microsoft.Exchange.WebServices.Data;
static void PageSearchItems(ExchangeService service, WellKnownFolderName folder)
{
    int pageSize = 5;
    int offset = 0;
    // Request one more item than your actual pageSize.
    // This will be used to detect a change to the result
    // set while paging.
    ItemView view = new ItemView(pageSize + 1, offset);
    view.PropertySet = new PropertySet(ItemSchema.Subject);
    view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Descending);
    view.Traversal = ItemTraversal.Shallow;
    bool moreItems = true;
    ItemId anchorId = null;
    while (moreItems)
    {
        try
        {
            FindItemsResults<Item> results = service.FindItems(folder, view);
            moreItems = results.MoreAvailable;
            if (moreItems && anchorId != null)
            {
                // Check the first result to make sure it matches
                // the last result (anchor) from the previous page.
                // If it doesn't, that means that something was added
                // or deleted since you started the search.
                if (results.Items.First<Item>().Id != anchorId)
                {
                    Console.WriteLine("The collection has changed while paging. Some results may be missed.");
                }
            }
            if (moreItems)
                view.Offset += pageSize;
                
            anchorId = results.Items.Last<Item>().Id;
            
            // Because you're including an additional item on the end of your results
            // as an anchor, you don't want to display it.
            // Set the number to loop as the smaller value between
            // the number of items in the collection and the page size.
            int displayCount = 0;
            if ((results.MoreAvailable == false && results.Items.Count > pageSize) || (results.Items.Count < pageSize))
            {
                displayCount = results.Items.Count;
            }
            else
            {
                displayCount = pageSize;
            }
            
            for (int i = 0; i < displayCount; i++)
            {
                Item item = results.Items[i];
                Console.WriteLine("Subject: {0}", item.Subject);
                Console.WriteLine("Id: {0}\n", item.Id.ToString());
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception while paging results: {0}", ex.Message);
        }
    }
}

例: EWS を使用して、ページング検索を実行する

ページングは、次の EWS 操作によってサポートされています。

EWS を使用している場合、アプリケーションでは IndexedPageItemView 要素または IndexedPageFolderView 要素でページングを構成し、RootFolder (FindItemResponseMessage) 要素または RootFolder (FindFolderResponseMessage) 要素でページングに関する情報がサーバーから送られます。

この要求の例では、FindItem 要求は、ユーザーの受信トレイにあるアイテムのリストの初めからのオフセットが 0 で始まる最大 6 アイテムの要求として送信されます。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" 
    xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" 
    xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2007_SP1" />
    <t:TimeZoneContext>
      <t:TimeZoneDefinition Id="Eastern Standard Time" />
    </t:TimeZoneContext>
  </soap:Header>
  <soap:Body>
    <m:FindItem Traversal="Shallow">
      <m:ItemShape>
        <t:BaseShape>IdOnly</t:BaseShape>
        <t:AdditionalProperties>
          <t:FieldURI FieldURI="item:Subject" />
        </t:AdditionalProperties>
      </m:ItemShape>
      <m:IndexedPageItemView MaxEntriesReturned="6" Offset="0" BasePoint="Beginning" />
      <m:ParentFolderIds>
        <t:DistinguishedFolderId Id="inbox" />
      </m:ParentFolderIds>
    </m:FindItem>
  </soap:Body>
</soap:Envelope>

サーバーは、6 アイテムを含む次の応答を返します。 さらに応答は、サーバー上の結果にアイテムが合計 8 あり、結果リストの最後のアイテムがこの応答には存在しないことを示します。

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="15" MinorVersion="0" MajorBuildNumber="775" MinorBuildNumber="35" Version="V2_4" 
        xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types" 
        xmlns="https://schemas.microsoft.com/exchange/services/2006/types" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:FindItemResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" 
        xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:FindItemResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:RootFolder IndexedPagingOffset="6" TotalItemsInView="8" IncludesLastItemInRange="false">
            <t:Items>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>Query</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>Update</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>Planning resources</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>Timeline</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>For your perusal</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>meeting notes</t:Subject>
              </t:Message>
            </t:Items>
          </m:RootFolder>
        </m:FindItemResponseMessage>
      </m:ResponseMessages>
    </m:FindItemResponse>
  </s:Body>
</s:Envelope>

この例では、同じ要求が送信されますが、今回の場合、Offset 属性は 5 に変更され、サーバーには、先頭からオフセット 5 から始まる最大 6 アイテムを返す必要があることが示されます。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" 
    xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types" 
    xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <t:RequestServerVersion Version="Exchange2007_SP1" />
    <t:TimeZoneContext>
      <t:TimeZoneDefinition Id="Eastern Standard Time" />
    </t:TimeZoneContext>
  </soap:Header>
  <soap:Body>
    <m:FindItem Traversal="Shallow">
      <m:ItemShape>
        <t:BaseShape>IdOnly</t:BaseShape>
        <t:AdditionalProperties>
          <t:FieldURI FieldURI="item:Subject" />
        </t:AdditionalProperties>
      </m:ItemShape>
      <m:IndexedPageItemView MaxEntriesReturned="6" Offset="5" BasePoint="Beginning" />
      <m:ParentFolderIds>
        <t:DistinguishedFolderId Id="inbox" />
      </m:ParentFolderIds>
    </m:FindItem>
  </soap:Body>
</soap:Envelope>

サーバーは、3 アイテムを含む次の応答を送信します。 応答は、サーバー上の結果の合計アイテム数が 8 のままで、結果リストの最後のアイテムがこの応答に含まれていることも示します。

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <h:ServerVersionInfo MajorVersion="15" MinorVersion="0" MajorBuildNumber="775" MinorBuildNumber="35" Version="V2_4" 
        xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types" 
        xmlns="https://schemas.microsoft.com/exchange/services/2006/types" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <m:FindItemResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages" 
    xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
      <m:ResponseMessages>
        <m:FindItemResponseMessage ResponseClass="Success">
          <m:ResponseCode>NoError</m:ResponseCode>
          <m:RootFolder IndexedPagingOffset="8" TotalItemsInView="8" IncludesLastItemInRange="true">
            <t:Items>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>meeting notes</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>Meeting notes</t:Subject>
              </t:Message>
              <t:Message>
                <t:ItemId Id="AAMkAGM2..." ChangeKey="CQAAABYA..." />
                <t:Subject>This cat is hilarious!</t:Subject>
              </t:Message>
            </t:Items>
          </m:RootFolder>
        </m:FindItemResponseMessage>
      </m:ResponseMessages>
    </m:FindItemResponse>
  </s:Body>
</s:Envelope>

関連項目