Microsoft Graph を使ってみよう : データの変更履歴取得

# デルタクエリを使った変更履歴の取得 

リソースのデータ量が多い場合やデータ同期シナリオでは、前回クエリ実行時からの差分のみを取得したいという要望があります。Microsoft Graph ではデルタクエリという機能を提供しており、以下のことが出来ます。

- 新しく作成されたデータの取得
- 更新されたデータの取得
- 削除されたデータの取得

デルタクエリでは「状態トークン」と呼ばれるトークンが返され、これにより変更を追跡します。
デルタクエリをサポートするリソースは [サポートされているリソース](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_overview#supported-resources) より確認できます。現時点では以下の通り。

- 標準として設定されている予定表の予定表ビュー (期間) 内のイベント
- グループ
- メール フォルダー
- フォルダー内のメッセージ
- 個人用連絡先フォルダー
- フォルダー内の個人用連絡先
- ユーザー
- ドライブの項目*

# イベントの変更履歴を取得する

早速試してみましょう。今回は [Graph エクスプローラー](https://developer.microsoft.com/ja-jp/graph/graph-explorer) を使って検証します。

1\. まず以下のクエリを実行して、2018 年 5 月の予定を取得。この時点では予定はない。

/me/calendarview/delta?startdatetime=2018-05-01T00:00:00Z&enddatetime=2018-05-31T23:59:59Z

結果:

```json:応答
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
    "@odata.nextLink": "https://graph.microsoft.com/v1.0/me/calendarview/delta?$skiptoken=R0usmcdvmMuZCBYV0hguCItPUeo0zi_0lf0otihj1fs4jZAkDlaAkpiqy7YMY3FAKsTbCC5-kLGOcpPAhpMhmKbsSnLingXn-31CwJqIXLMnomfJEIXiGJdSCaVkEunsLjOx7oL8umIWB-RytlvKbLq8c85E6-nBD9o1vPlnqCZEwxJ6QYmmajqpkaLMkqIhsWVAtGzJEDtNf7kEwkTKdQ._dYpM3OgRmC8LnUvYLpCU-NKy1scXd5KIoot6_4Prqs",
    "value": []
}
```

2\. @odata.nextLink が　@odata.daltaLink になるまでページング繰り返し、deltaLink が取得できたら予定表で予定を 1 件追加。

<img width="672" alt="Capture.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/214116/e7ead684-0df5-4923-b5e6-14cb3b3585cf.png">

3\. 先ほど取得した @odata.deltaLink を実行。作成されたレコードが返ることと、createdDateTime および lastModifiedDateTime が同じ時刻であることを確認。

```json:応答
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
    "@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/calendarview/delta?$deltatoken=R0usmcdvmMuZCBYV0hguCImcxcIep98dKtAsdXFvHJvhelEYHLeJ4k3I2SuEU7A22h034OEHu8BokbCE1zX0xRgtEDq5XpoiBIlTmAmgDAjnTi7ht-xOKkSH390DExUkk1qRiUS9tAlkaRoeQtKZ_xp4n8qsB0AXb7MEU8QVs4C_3vD309Y36wocWkLEmGD9lZ4ws8FmZDwJnKeOGYJJog.iVXCfKImML-ErheRDTUfai9FZwkiGhHhWr1aMecJdc8",
    "value": [
        {
            "@odata.type": "#microsoft.graph.event",
            "@odata.etag": "W/\"bzSYBZOD8UWO6lBMtfFSegAAAapYfA==\"",
            "createdDateTime": "2018-05-10T05:26:17.4232126Z",
            "lastModifiedDateTime": "2018-05-10T05:26:17.4700894Z",
            "changeKey": "bzSYBZOD8UWO6lBMtfFSegAAAapYfA==",
            "categories": [],
            "originalStartTimeZone": "UTC",
            "originalEndTimeZone": "UTC",
            "iCalUId": "040000008200E00074C5B7101A82E008000000003EF6CE6B1FE8D30100000000000000001000000040A3B91E800E584C897F708B758BB039",
            "reminderMinutesBeforeStart": 15,
            "isReminderOn": false,
            "hasAttachments": false,
            "subject": "de:code 2018 1日目",
            "bodyPreview": "",
            "importance": "normal",
            "sensitivity": "normal",
            "isAllDay": true,
            "isCancelled": false,
            "isOrganizer": true,
            "responseRequested": true,
            "seriesMasterId": null,
            "showAs": "free",
            "type": "singleInstance",
            "webLink": "https://outlook.office365.com/owa/?itemid=AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAABqwu8AAA%3D&exvsurl=1&path=/calendar/item",
            "onlineMeetingUrl": null,
            "id": "AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAABqwu8AAA=",
            "responseStatus": {
                "response": "organizer",
                "time": "0001-01-01T00:00:00Z"
            },
            "body": {
                "contentType": "html",
                "content": "<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta content=\"text/html; charset=iso-2022-jp\">\r\n<style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style>\r\n</head>\r\n<body dir=\"ltr\">\r\n<div id=\"divtagdefaultwrapper\" dir=\"ltr\" style=\"font-size:12pt; color:#000000; font-family:Calibri,Helvetica,sans-serif\">\r\n<p style=\"margin-top:0; margin-bottom:0\"><br>\r\n</p>\r\n</div>\r\n</body>\r\n</html>\r\n"
            },
            "start": {
                "dateTime": "2018-05-22T00:00:00.0000000",
                "timeZone": "UTC"
            },
            "end": {
                "dateTime": "2018-05-23T00:00:00.0000000",
                "timeZone": "UTC"
            },
            "location": {
                "displayName": "de",
                "locationType": "default",
                "uniqueId": "de",
                "uniqueIdType": "private"
            },
            "locations": [
                {
                    "displayName": "de",
                    "locationType": "default",
                    "uniqueId": "de",
                    "uniqueIdType": "private"
                }
            ],
            "attendees": [],
            "organizer": {
                "emailAddress": {
                    "name": "中村 憲一郎",
                    "address": "admin@graphdemo01.onmicrosoft.com"
                }
            }
        }
    ]
}
```

4\. 予定を変更。今回は場所を間違えて入れたため、場所の情報を削除。その後  @odata.deltaLink のアドレスを実行。返った情報の lastModifiedDateTime が更新されていることと、場所の情報がクリアされている点を確認。

```json:応答
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
    "@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/calendarview/delta?$deltatoken=R0usmcdvmMuZCBYV0hguCNY5fuMoHUnvpkMnIjveWajrIqvs8DH-ojxwJo4VL0fLw_7p0KYD3Nhf465OPAToc32squmcu811Uy-hGFI9_pbbAa8JutwJ1VlJs82TMDxfKlyTGIMCpWMyy6IcY9hK5rqMW59PxPl8rluF5ylgd6iN86efFyWYiR8r780dFRzdCC0dYd6zyr4QUGiNtt3nlg.YvHiTYhxmkYhZ2Djh-tZaymWX0kWJayVpzBPiT4UaSY",
    "value": [
        {
            "@odata.type": "#microsoft.graph.event",
            "@odata.etag": "W/\"bzSYBZOD8UWO6lBMtfFSegAAAapYiA==\"",
            "createdDateTime": "2018-05-10T05:26:17.4232126Z",
            "lastModifiedDateTime": "2018-05-10T05:29:31.7069764Z",
            "changeKey": "bzSYBZOD8UWO6lBMtfFSegAAAapYiA==",
            "categories": [],
            "originalStartTimeZone": "UTC",
            "originalEndTimeZone": "UTC",
            "iCalUId": "040000008200E00074C5B7101A82E008000000003EF6CE6B1FE8D30100000000000000001000000040A3B91E800E584C897F708B758BB039",
            "reminderMinutesBeforeStart": 15,
            "isReminderOn": false,
            "hasAttachments": false,
            "subject": "de:code 2018 1日目",
            "bodyPreview": "",
            "importance": "normal",
            "sensitivity": "normal",
            "isAllDay": true,
            "isCancelled": false,
            "isOrganizer": true,
            "responseRequested": true,
            "seriesMasterId": null,
            "showAs": "free",
            "type": "singleInstance",
            "webLink": "https://outlook.office365.com/owa/?itemid=AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAABqwu8AAA%3D&exvsurl=1&path=/calendar/item",
            "onlineMeetingUrl": null,
            "id": "AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAABqwu8AAA=",
            "responseStatus": {
                "response": "organizer",
                "time": "0001-01-01T00:00:00Z"
            },
            "body": {
                "contentType": "html",
                "content": "<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta content=\"text/html; charset=iso-2022-jp\">\r\n<style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style>\r\n</head>\r\n<body dir=\"ltr\">\r\n<div id=\"divtagdefaultwrapper\" dir=\"ltr\" style=\"font-size:12pt; color:#000000; font-family:Calibri,Helvetica,sans-serif\">\r\n<p style=\"margin-top:0; margin-bottom:0\"><br>\r\n</p>\r\n</div>\r\n</body>\r\n</html>\r\n"
            },
            "start": {
                "dateTime": "2018-05-22T00:00:00.0000000",
                "timeZone": "UTC"
            },
            "end": {
                "dateTime": "2018-05-23T00:00:00.0000000",
                "timeZone": "UTC"
            },
            "location": {
                "displayName": "",
                "locationType": "default",
                "uniqueIdType": "unknown",
                "address": {},
                "coordinates": {}
            },
            "locations": [],
            "attendees": [],
            "organizer": {
                "emailAddress": {
                    "name": "中村 憲一郎",
                    "address": "admin@graphdemo01.onmicrosoft.com"
                }
            }
        }
    ]
}
```
5\. 予定を削除して、再度最新の @odata.deltaLink のアドレスを実行。削除されたため @removed に理由が記述。

```json:応答

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
    "@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/calendarview/delta?$deltatoken=R0usmcdvmMuZCBYV0hguCNY5fuMoHUnvpkMnIjveWajrIqvs8DH-ojxwJo4VL0fLw_7p0KYD3Nhf465OPAToc2b19RvNvjNzdeF6tnQ3T4l0zB_5YiFjZ7soQKOYxb3wqIAGCEA3ztbyyeEZwWkRYQInj6tPP51BhbRv4O1hv4vp0MYB4nS-InfFCHNaY2DNHEXeJY_NCEkUEwTvJaZYTw.N-ECSXbzJ4wBG-3cm256ZaFClYcwTrYRVtpTCErQpKI",
    "value": [
        {
            "@odata.type": "#microsoft.graph.event",
            "id": "AAMkAGExMTQzMTU1LTE3ZGYtNDU1Ny1iMzczLWQ1MmU0YjQxNmEzNgBGAAAAAACFQuNJIe4VSIJxcTi2cbakBwBvNJgFk4PxRY7qUEy18VJ6AAAAAAENAABvNJgFk4PxRY7qUEy18VJ6AAABqwu8AAA=",
            "@removed": {
                "reason": "deleted"
            }
        }
    ]
}
```
## SDK を使う場合

次に C# で SDK を使う場合についてやり方を紹介します。

**デルタクエリ**
上記と同じデルタクエリは以下のコードで実行可能です。

```csharp:C#
var response = await graphClient.Me.CalendarView.Delta().Request(new[]
            {
                new QueryOption("startDateTime", "2018-05-01T00:00:00Z"),
                new QueryOption("endDateTime", "2018-05-31T23:59:59Z")
            }).GetAsync();
```
**NextLink**
戻り値に NextLink がある場合は、以下のコードが実行可能です。

```csharp:C#
response = await response.NextPageRequest.GetAsync();
```

**DeltaLink**
NextLink がない場合は、以下の方法で DeltaLink を取り出し NextPage としてセットします。

```csharp:C#
response.InitializeNextPageRequest(
    graphClient, 
    response.AdditionalData["@odata.deltaLink"].ToString());
response = await response.NextPageRequest.GetAsync();
```
# @odata.deltaLink 

デルタクエリを実行すると、結果に @odata.deltaLink が含まれますが、動作は以下のようになります。

1\. デルタ関数を使用して GET 要求を呼び出す。
2\. Microsoft Graph が応答を送信。

- nextLink URL が返される場合、セッションに取得するデータの追加ページあり。
- deltaLink URL が返される場合、返されるリソースの既存の状態に関するデータはなし。

3\. 変更履歴を取得するには deltaLink URL を使用して、新しい要求を実行。

# まとめ

データの変更履歴取得はシナリオによってパフォーマンス向上に大きく寄与するため、是非お試しください。

[目次へ戻る](https://qiita.com/kenakamu/items/6a1ca508dc093779eefa)

# 参照

[デルタ クエリを使用して、Microsoft Graph データの変更を追跡する](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_overview)
[カレンダー ビューのイベントへの増分の変更を取得する (例)](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_events)
[グループに対する増分の変更を取得する (例)](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_groups)
[フォルダー内のメッセージへの増分の変更を取得する (例)](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_messages)
[ユーザーに対する増分の変更を取得する (例)](https://developer.microsoft.com/ja-jp/graph/docs/concepts/delta_query_users)