CData Software Blog

クラウド連携のCData Software の技術ブログです。

Dataverse / Dynamics 365 Web API のページネーション:5000件以上のデータを取得する

f:id:sugimomoto:20210531154255p:plain

こんにちは。CData Software Japanリードエンジニアの杉本です。

今回はDynamics 365 / Dataverse Web API を利用する上でおそらく一番最初に知っておきたいポイントの一つであるページネーションについてちょっと書いておきたいと思います。

結構今更感はあるのですが、先入観でちょっと勘違いしていたことがあったので、そこも含めて。

Dynamics 365 / Dataverse Web API のデフォルト取得件数

Dynamics 365 / Dataverse Web API なんですが、GETリクエストでデータを取得した場合、デフォルトでは5000件の結果が取得できます。

docs.microsoft.com

例えば取引先企業(accounts)を取得しようとして「GET https://XXXXX.crm7.dynamics.com/api/data/v9.2/accounts」リクエストすると最大で5000件、以下のようにレコードを取得できます。(見づらいので、カラムは絞っています。)

{
    "@odata.context": "https://XXXXX.crm7.dynamics.com/api/data/v9.0/$metadata#accounts",
    "value": [
        {
            "@odata.etag": "W/\"7816473\"",
            "name": "Sample 1",
            "accountid": "d7d7cee5-a8c1-eb11-bacc-000d3a40e0ac"
        },
        /* 割愛。5000件まで取得できる */
        {
            "@odata.etag": "W/\"7816473\"",
            "name": "Sample 2",
            "accountid": "d7d7cee5-a8c1-eb11-bacc-000d3a40e0ac"
        }
    ],
    "@odata.nextLink": "https://XXXXX.crm7.dynamics.com/api/data/v9.0/accounts?$skiptoken=%3Ccookie%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%2522%253e%253caccountid%2520last%253d%2522%257bD7D7CEE5-A8C1-EB11-BACC-000D3A40E0AC%257d%2522%2520first%253d%2522%257b93C71621-BD9F-E711-8122-000D3A2BA2EA%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E"
}

そのため、上限値で5000件以降のデータを取得したい場合はページネーションのリクエストを別途続けて行う必要があります。

ちなみに、取得する件数を制限したい場合は以下のように「$top」のクエリパラメータを付与することで、制限できます。

「GET https://XXXX.crm7.dynamics.com/api/data/v9.2/accounts?$top=5

ページネーションの方法について

5000件以降のデータを取得する場合は「nextPageToken」のクエリパラメータを生成して、リクエストする必要があります。

docs.microsoft.com

これは前述のレスポンスに含まれている「@odata.netLink」をそのままGETリクエストで再度指定するだけで取得できます。

{
    "@odata.context": "https://XXXXX.crm7.dynamics.com/api/data/v9.0/$metadata#accounts",
    "value": [
        {
            "@odata.etag": "W/\"7816473\"",
            "name": "Sample 1",
            "accountid": "d7d7cee5-a8c1-eb11-bacc-000d3a40e0ac"
        },
        /* 割愛。5000件まで取得できる */
        {
            "@odata.etag": "W/\"7816473\"",
            "name": "Sample 2",
            "accountid": "d7d7cee5-a8c1-eb11-bacc-000d3a40e0ac"
        }
    ],
/* 以下の部分 */
    "@odata.nextLink": "https://XXXXX.crm7.dynamics.com/api/data/v9.0/accounts?$skiptoken=%3Ccookie%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%2522%253e%253caccountid%2520last%253d%2522%257bD7D7CEE5-A8C1-EB11-BACC-000D3A40E0AC%257d%2522%2520first%253d%2522%257b93C71621-BD9F-E711-8122-000D3A2BA2EA%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E"
}

つまり、特に悩まず以下のようにリクエストします。よく見ると、「$skiptoken」というクエリパラメータが指定されていることがわかりますね。これがページネーションのリクエストです。

「GET https://XXXXX.crm7.dynamics.com/api/data/v9.0/accounts?$skiptoken=%3Ccookie%20pagenumber=%222%22%20pagingcookie=%22%253ccookie%2520page%253d%25221%2522%253e%253caccountid%2520last%253d%2522%257bD7D7CEE5-A8C1-EB11-BACC-000D3A40E0AC%257d%2522%2520first%253d%2522%257b93C71621-BD9F-E711-8122-000D3A2BA2EA%257d%2522%2520%252f%253e%253c%252fcookie%253e%22%20istracking=%22False%22%20/%3E

REST Ful APIの原則にある「ハイパーメディア」に則って提供されている機能で良いですね。デベロッパーは今自分がどのページや何件取得したか、みたいなことをプログラムの中で把握する必要がありません。

ただ、自身でこの範囲を取得したい(例えば10,000~15,000件の間)、ということには対応できないので注意しましょう。

ちなみにただ試すだけなら、ブラウザからも実行できます。デフォルトだと取得項目が多くて重いので、$selectで絞り込むと良いでしょう。

https://XXXX.crm7.dynamics.com/api/data/v9.0/accounts?$select=name

以下のように「odataNextLink」が取れるので、これをコピペして、ブラウザURLに貼り付け直すだけです。

f:id:sugimomoto:20210531153936p:plain

ちょっとわかりづらいですが、NextPageが取得できました。

f:id:sugimomoto:20210531153944p:plain

ODataのSkipってどうなの?

これは私が先入観で躓いたのですが、OData的には「nextPageToken」ってクエリパタメータの定義は無いんですよね。

なので、実は「$top」と「$skip」のクエリパラメータを組み合わせてリクエストするのがベーシックな方式です。

docs.oasis-open.org

もしかすると、ODataを使ったSDKの中には、skipパラメータを利用するものがデフォルトであったりするかもしれないので、注意しましょう。

一応、実際にskipクエリパラメータを試すと、以下のようなエラーメッセージが返されます。

「GET https://XXXXXcrm7.dynamics.com/api/data/v9.2/accounts?$select=name&skip=2

{"error":{"code":"0x0","message":"The query parameter skip is not supported"}}

ちなみに、Office365 Graph APIはリソースによってskipが使えたり、skipToken が使えたりと変わります。このあたりはそれぞれのサービスのデータの持ち方、キャッシュの取り扱いによって変わっている雰囲気ですね。

docs.microsoft.com

CData Driver 内部の挙動について

CData Dynamics 365 / Dataverse Driverでは、内部で自動的にページネーションを行い、SQLで指定された件数分取得するまで、もしくはnextPageTokenが提示されなくなるまで、リクエストを繰り返します。

www.cdata.com

www.cdata.com

例えば、以下のようなSQL文をリクエストして、その対象レコードが5000件以上存在した場合は

「SELECT name FROM accounts;」

f:id:sugimomoto:20210531153952p:plain

内部ではnextPageToken(skiptoken)を使って、すべてのレコードを取得するまで複数回リクエストが実行されます。

f:id:sugimomoto:20210531153959p:plain

現状、以前調べたDynamics 365 / Dataverse の API Limitは厳しいものではありませんでしたが、こういった形でAPIリクエストを消費する可能性があるので、データ取得の際には注意しましょう。

www.cdatablog.jp