Dynamics CRM 2016 SDK 新機能: Web API その 8: バッチ処理実行

みなさん、こんにちは。

前回に引き続き、Dynamics CRM 2016 SDK の新機能として、正式版と
なった Web API について紹介します。

今回は Web API でのバッチ処理実行について紹介します。

概要

Web  API は複数の操作を同じトランザクションとして実行することが可能
です。取引先企業レコードと同時に関連する取引先担当者を作成するなど、
関連があるレコードの同時処理は、通常の作成処理が利用できます。よって
バッチ処理に適したシナリオは、お互いに関連が無いレコードの操作を同じ
トランザクション内で実行したい場合となります。

複数レコードのバッチ作成

取引先企業 2 件を同じトランザクション内で作成する例を紹介します。
バッチ処理実行を行うためには、複数の要求を MutlPartContent の
一部として追加していきます。

参照の追加

プログラム内で利用するクラスを含むアセンブリを参照します。

1. 参照を右クリックして「参照の追加」をクリックします。

image

2. アセンブリ | 拡張より System.Net.Http.Formatting を選択して
「OK」をクリックします。

image

プログラムの実装

1. 前回利用した Visual Studio ソリューションを開き、Program.cs
ファイルを開きます。新しく以下のメソッドを追加します。

public async Task RunBatch(string accessToken)
{
    // HttpClient の作成
    using (HttpClient httpClient = new HttpClient())
    {
        // Web API アドレスの作成
        string serviceUrl = serverUrl + "/api/data/v8.0/";
        // ヘッダーの設定
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    }
}

2. Main メソッドの以下のコードを書き換えて、新しいメソッドを
呼ぶように変更します。

元)  
Task.WaitAll(Task.Run(async () => await app.RunPaging(result.AccessToken)));

変更後)
Task.WaitAll(Task.Run(async () => await app.RunBatch(result.AccessToken)));

3. 新しく追加した RunBatchメソッド内に以下のコードを追加して、
レコードの作成要求を作成します。またトランザクションの処理番号として
固有の Content-ID をヘッダーとして追加します。

// 取引先企業レコードの作成要求
HttpRequestMessage addAccount1 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");
addAccount1.Content = new StringContent("{'name':'test1'}", Encoding.UTF8, "application/json");
addAccount1.Content.Headers.Add("Content-ID", "1");

4. 以下のコードを追加して、作成要求を HttpContent にします。

// 作成要求をコンテントに追加
HttpMessageContent addAccountContent1 = new HttpMessageContent(addAccount1);

5. 以下のコードを追加して必要なヘッダー情報を指定します。

// ヘッダーの指定
addAccountContent1.Headers.Remove("Content-Type");
addAccountContent1.Headers.Add("Content-Type", "application/http");
addAccountContent1.Headers.Add("Content-Transfer-Encoding", "binary");

6. 同様に以下のコードで 2 つ目の作成要求を作成します。

// 取引先企業レコードの作成要求
HttpRequestMessage addAccount2 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");
addAccount2.Content = new StringContent("{'name':'test2'}", Encoding.UTF8, "application/json");
addAccount2.Content.Headers.Add("Content-ID", "2");

HttpMessageContent addAccountContent2 = new HttpMessageContent(addAccount2);
addAccountContent2.Headers.Remove("Content-Type");
addAccountContent2.Headers.Add("Content-Type", "application/http");
addAccountContent2.Headers.Add("Content-Transfer-Encoding", "binary");

7. 以下のコードを追加して MultipartContent を作成します。これは
トランザクションに含める操作をすべて保持するコンテンツとなります。

// ChangeSet を作成
MultipartContent changeSetContent = new MultipartContent("mixed", "changeset_boundary");

8. 作成済みのコンテンツを追加します。

// 作成要求の追加
changeSetContent.Add(addAccountContent1);
changeSetContent.Add(addAccountContent2);

9. 次に以下のコードを追加してバッチの本体を作成し、上記のコンテンツを
追加します。

// バッチ用のコンテンツを作成し ChangeSet を追加
MultipartContent batchcontent = new MultipartContent("mixed", "batch_boundary");
batchcontent.Add(changeSetContent);

10. 以下のコードを追加して要求を実行します。

// $batch エンドポイントへ要求の実行
HttpResponseMessage batchRes = await httpClient.PostAsync(serviceUrl + "$batch", batchcontent);

動作確認

1. F5 キーを押下してプログラムを実行します。

2. 認証ダイアログが表示されたらログインします。

image

3. プログラムが完了したらレコードが作成されていることを
確認します。

image

トランザクション処理の確認

次に処理に失敗する要求を追加して、トランザクション処理が実際に
機能するか確認します。事前に先ほど追加したレコードは削除します。

プログラムの変更

バッチ操作実行直前に以下のコードを追加します。以下のコードは
Guid が空のレコードを作成しようとするため失敗します。

// 処理が失敗するレコードの作成要求
HttpRequestMessage addAccount3 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");

addAccount3.Content = new StringContent("{'name':'test3','accountid':'00000000-0000-0000-0000-000000000000'}", Encoding.UTF8, "application/json");
addAccount3.Content.Headers.Add("Content-ID", "3");

HttpMessageContent addAccountContent3 = new HttpMessageContent(addAccount3);
addAccountContent3.Headers.Remove("Content-Type");
addAccountContent3.Headers.Add("Content-Type", "application/http");
addAccountContent3.Headers.Add("Content-Transfer-Encoding", "binary");

changeSetContent.Add(addAccountContent3);

動作確認

1. F5 キーを押下してプログラムを実行します。

2. 認証ダイアログが表示されたらログインします。

3. レコードが 1 件も作成されていない事を確認します。

以下に今回追加したメソッドを示します。

public async Task RunBatch(string accessToken)
{
    // HttpClient の作成
    using (HttpClient httpClient = new HttpClient())
    {
        // Web API アドレスの作成
        string serviceUrl = serverUrl + "/api/data/v8.0/";
        // ヘッダーの設定
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        // 取引先企業レコードの作成要求
        HttpRequestMessage addAccount1 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");
        addAccount1.Content = new StringContent("{'name':'test1'}", Encoding.UTF8, "application/json");
        addAccount1.Content.Headers.Add("Content-ID", "1");

        // 作成要求をコンテントに追加
        HttpMessageContent addAccountContent1 = new HttpMessageContent(addAccount1);
        // ヘッダーの指定
        addAccountContent1.Headers.Remove("Content-Type");
        addAccountContent1.Headers.Add("Content-Type", "application/http");
        addAccountContent1.Headers.Add("Content-Transfer-Encoding", "binary");

        // 取引先企業レコードの作成要求
        HttpRequestMessage addAccount2 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");
        addAccount2.Content = new StringContent("{'name':'test2'}", Encoding.UTF8, "application/json");
        addAccount2.Content.Headers.Add("Content-ID", "2");
        HttpMessageContent addAccountContent2 = new HttpMessageContent(addAccount2);
        addAccountContent2.Headers.Remove("Content-Type");
        addAccountContent2.Headers.Add("Content-Type", "application/http");
        addAccountContent2.Headers.Add("Content-Transfer-Encoding", "binary");

        // ChangeSet を作成
        MultipartContent changeSetContent = new MultipartContent("mixed", "changeset_boundary");
        // 作成要求の追加
        changeSetContent.Add(addAccountContent1);
        changeSetContent.Add(addAccountContent2);
        // 処理が失敗するレコードの作成要求
        HttpRequestMessage addAccount3 = new HttpRequestMessage(HttpMethod.Post, serviceUrl + "accounts");
        addAccount3.Content = new StringContent("{'name':'test3','accountid':'00000000-0000-0000-0000-000000000000'}", Encoding.UTF8, "application/json");
        addAccount3.Content.Headers.Add("Content-ID", "3");

        HttpMessageContent addAccountContent3 = new HttpMessageContent(addAccount3);
        addAccountContent3.Headers.Remove("Content-Type");
        addAccountContent3.Headers.Add("Content-Type", "application/http");
        addAccountContent3.Headers.Add("Content-Transfer-Encoding", "binary");

        changeSetContent.Add(addAccountContent3);

        // バッチ用のコンテンツを作成し ChangeSet を追加
        MultipartContent batchcontent = new MultipartContent("mixed", "batch_boundary");
        batchcontent.Add(changeSetContent);

        // $batch エンドポイントへ要求の実行
        HttpResponseMessage batchRes = await httpClient.PostAsync(serviceUrl + "$batch", batchcontent);
    }
}

まとめ

Web サービスを利用した処理における、トランザクションの利用の
サポートは強く要望があった機能の 1 つです。これまではプラグインを
活用するなど工夫が必要でしたが、これからは任意の複数の処理実行の
トランザクション管理ができますので、是非お試しください!

- 中村 憲一郎

※本情報の内容(添付文書、リンク先などを含む)は、作成日時点でのものであり、予告なく変更される場合があります