使用JS通过Web API执行批量操作,多个操作是一个事务!

关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复235或者20161105可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me 。

Dynamics CRM的Web API新增了一个功能,就是 Execute batch operations using the Web API ,我也参考了
pham.hongnghiep 的文章 involve Batch Operation CRM 2016 Web Api ,这个功能在某些情况下可能很有用。因为它能通过JS调用一次Web API执行多个操作(这个操作是指一个动作,比如新增一条记录,删除一条记录等,不是流程类型中的操作),而且这多个操作是同一个事物,都成功或者都失败!这个功能和我前面文章 执行插件的替代方式:用JS调用操作 功能类似,但是不需要创建操作,这个是优点,缺点是构造这个POST请求的内容和解析返回的内容比较复杂,都是文本,不能方便的转换成JSON。
为了解释这个概念,我虚拟出一个需求:
1)创建一个罗勇测试实体(ly_test)记录及两个罗勇测试辅助实体(ly_testsub)记录,
2)为没有关联关系的记录,某个固定的客户增加一个任务
3更新本批次创建的测试实体的一个字段的值,注意$1的运用,它是引用同一个change set中的增加了Content-ID头创建记录的URL)。
写代码的时候要注意:
1. changeset 结尾的时候有 -- ,开始的时候则没有,batch也是。
2. 创建父子关系的实体记录不要通过batch,通过深度创建即可,可有参考我的博文:Dynamics CRM 中Web API中的深度创建(Deep Insert) .
3. $1等的引用不能放到request body中的json中,反正我试了不行,也可能是我没有找到正确的方法。
4. 一个changeset中的一个操作是从 --changeset 部分开始,到下一个--changeset,注意其中Content-ID后面要加空行,真正请求的内容前面要加空行,也就是 Content-Type: application/json;type=entry 后面。
下面奉上一个完整的代码:
function submit() {
    var clientURL = Xrm.Page.context.getClientUrl();
    var batchId = getRandomString(12);
    var changesetId = getRandomString(12);
    var requestMsg = ["--batch_" + batchId];
    requestMsg.push("Content-Type: multipart/mixed;boundary=changeset_" + changesetId);
    requestMsg.push("");
    requestMsg.push("--changeset_" + changesetId);
    requestMsg.push("Content-Type: application/http");
    requestMsg.push("Content-Transfer-Encoding:binary");
    requestMsg.push("Content-ID: 1");
    requestMsg.push("");
    requestMsg.push("POST " + clientURL + "/api/data/v8.1/ly_tests HTTP/1.1");
    requestMsg.push("Content-Type: application/json;type=entry");
    requestMsg.push("");//注意这里要加空行
    var subMsg1 = {};
    subMsg1.ly_name = "批量操作创建的罗勇测试记录";
    subMsg1["ly_Lookup@odata.bind"] = "/accounts(CE23165A-3AA3-E511-80C7-000D3A807EC7)";
    subMsg1["ly_ly_test_ly_testsub_Test"] = [];
    subMsg1["ly_ly_test_ly_testsub_Test"].push({ "ly_name": "批量操作创建的罗勇测试辅助实体记录1" });
    subMsg1["ly_ly_test_ly_testsub_Test"].push({ "ly_name": "批量操作创建的罗勇测试辅助实体记录2" });
    requestMsg.push(JSON.stringify(subMsg1));
    requestMsg.push("--changeset_" + changesetId);
    requestMsg.push("Content-Type: application/http");
    requestMsg.push("Content-Transfer-Encoding:binary");
    requestMsg.push("Content-ID: 2");
    requestMsg.push("");//注意这里要加空行
    requestMsg.push("POST " + clientURL + "/api/data/v8.1/tasks HTTP/1.1");
    requestMsg.push("Content-Type: application/json;type=entry");
    requestMsg.push("");//注意这里要加空行
    requestMsg.push("{'subject':'批量操作创建的任务','regardingobjectid_account_task@odata.bind':'https://demo.luoyong.me/api/data/v8.1/accounts(C223165A-3AA3-E511-80C7-000D3A807EC7)'}");
    requestMsg.push("--changeset_" + changesetId);
    requestMsg.push("Content-Type: application/http");
    requestMsg.push("Content-Transfer-Encoding:binary");
    requestMsg.push("Content-ID: 3");
    requestMsg.push("");
    requestMsg.push("PATCH $1 HTTP/1.1");
    requestMsg.push("Content-Type: application/json;type=entry");
    requestMsg.push("");//注意这里要加空行
    requestMsg.push("{'ly_singlelinetext':'更新了批量操作创建的罗勇测试实体记录'}");
    requestMsg.push("--changeset_" + changesetId + "--");//changeset结束这里前面不要加空行
    requestMsg.push("");//注意这里要加空行
    requestMsg.push("--batch_" + batchId + "--");//batch结束前面加空行
    executeBatch(clientURL, batchId, requestMsg.join("\n"), function (responseText) {
        Xrm.Utility.alertDialog("执行后返回status=200,也有可能有错误:" + responseText);
    }, function (responseText) {
        Xrm.Utility.alertDialog("执行出错:" + responseText);
    });
}

function executeBatch(clientURL, batchId, requestMsg, successCallback, errorCallback) {
    var req = new XMLHttpRequest()
    req.open("POST", encodeURI(clientURL + "/api/data/v8.1/$batch", true));//true是异步请求,false是同步请求
    req.setRequestHeader("Content-Type", "multipart/mixed;boundary=batch_" + batchId);
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.onreadystatechange = function () {
        if (this.readyState == 4 /* complete */) {
            req.onreadystatechange = null;
            if (this.status == 200) {//204代表成功无返回值
                successCallback(this.responseText);
            }
            else {
                errorCallback(this.responseText);
            }
        }
    };
    req.send(requestMsg);
}

function getRandomString(len, charSet) {
    charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var randomString = '';
    for (var i = 0; i < len; i++) {
        var randomPoz = Math.floor(Math.random() * charSet.length);
        randomString += charSet.substring(randomPoz, randomPoz + 1);
    }
    return randomString;
}

 

 
下面是如果使用fiddler模拟的话是如下操作:
1) POST到的URL是 https://demo.luoyong.me/api/data/v8.1/$batch
2) 请求头是:注意最后一个Authorization: Bearer 是认证信息,根据实际情况需要的加上。

 

Content-Type: multipart/mixed;boundary=batch_AAA1234

Accept: application/json
OData-MaxVersion: 4.0
OData-Version: 4.0
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IlRBRzN5S3hrZW04eHppQmpmS3RSUFBoQ1liWSJ9.eyJhdWQiOiJodHRwczovL2RlbW8ubHVveW9uZy5tZS8iLCJpc3MiOiJodHRwOi8vc3RzLmx1b3lvbmcubWUvYWRmcy9zZXJ2aWNlcy90cnVzdCIsImlhdCI6MTQ3ODMwNzM5MiwiZXhwIjoxNDc4MzM2MTkyLCJ1cG4iOiJjcm1hZG1pbkBsdW95b25nLm1lIiwicHJpbWFyeXNpZCI6IlMtMS01LTIxLTY1Mzc0MDc5OC0yNDUxMzM4NTMyLTMwMjU3ODY3ODktMTEwNCIsInVuaXF1ZV9uYW1lIjoiTFVPWU9OR1xcY3JtYWRtaW4iLCJhdXRoX3RpbWUiOiIyMDE2LTExLTA1VDAwOjU2OjMyLjMxM1oiLCJhdXRobWV0aG9kIjoidXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQiLCJ2ZXIiOiIxLjAiLCJhcHBpZCI6ImJhMTA2MjY1LWZiM2ItNDllMC1hMGU4LTY4NDBiM2Q3MWFjMiJ9.M8RXfCpBLOoJSazOkmjMIJYyYfYxVGmwojOjO4Pd4Yn9EtbeYK1t0tisP19WTsniHvsRXWaD5bwtLMxQL3DMCOJ1Ftsi7o6gr9U_9URRXffHOqPGOwO5n2Ofxga9pqETi2HPO0PpsyD0hOwMsBkZXCuwjOiYzNdbIY4bq0FDOc4iL7HYTd6xvYAOC8JhNj-H6QuqhrFxRPSrkcKt9Pgkg7T-oblWqZwZEV0sIRbm00pYLSIcPVRiFUmgBQUu7kwqFcwRDgLe7VlSTGlaTRtZphwMB-cYO3LebxDABeMuosZAZPa47g4s8xLpNb5lt9sd365Oebut4jUDhT1VIhNKPw

3)请求体是:

--batch_AAA1234
Content-Type: multipart/mixed;boundary=changeset_BBB4567

--changeset_BBB4567
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 1

POST https://demo.luoyong.me/api/data/v8.1/ly_tests HTTP/1.1
Content-Type: application/json;type=entry

{"ly_name":"批量操作创建的罗勇测试记录","ly_Lookup@odata.bind":"/accounts(CE23165A-3AA3-E511-80C7-000D3A807EC7)","ly_integer":10,"ly_ly_test_ly_testsub_Test":[{'ly_name':'批量操作创建的罗勇测试辅助实体记录1'},{'ly_name':'批量操作创建的罗勇测试辅助实体记录2'}]}

--changeset_BBB4567
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 2

POST https://demo.luoyong.me/api/data/v8.1/tasks HTTP/1.1
Content-Type: application/json;type=entry

{"subject":"批量操作创建的任务","regardingobjectid_account_task@odata.bind":"https://demo.luoyong.me/api/data/v8.1/accounts(C223165A-3AA3-E511-80C7-000D3A807EC7)"}

--changeset_BBB4567
Content-Type: application/http
Content-Transfer-Encoding:binary
Content-ID: 3

PATCH $1 HTTP/1.1
Content-Type: application/json;type=entry

{"ly_singlelinetext":"更新了批量操作创建的罗勇测试实体记录"}

--changeset_BBB4567--

--batch_AAA1234--

我帖出一个返回结果,可以看到是普通文本:
 
文本我粘贴放到下面:

"--batchresponse_8071c8bc-f577-44a6-a246-e27c1ec027e2
?Content-Type: multipart/mixed; boundary=changesetresponse_c5387a7e-c5c6-4ea5-95bd-13df85d67046
?
?--changesetresponse_c5387a7e-c5c6-4ea5-95bd-13df85d67046
?Content-Type: application/http
?Content-Transfer-Encoding: binary
?Content-ID: 1
?
?HTTP/1.1 204 No Content
?OData-Version: 4.0
?Location: https://demo.luoyong.me/api/data/v8.1/ly_tests(6c358f7d-54a3-e611-816b-000d3a80c8b8)
?OData-EntityId: https://demo.luoyong.me/api/data/v8.1/ly_tests(6c358f7d-54a3-e611-816b-000d3a80c8b8)
?Access-Control-Expose-Headers: Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
?
?
?--changesetresponse_c5387a7e-c5c6-4ea5-95bd-13df85d67046
?Content-Type: application/http
?Content-Transfer-Encoding: binary
?Content-ID: 2
?
?HTTP/1.1 204 No Content
?OData-Version: 4.0
?Location: https://demo.luoyong.me/api/data/v8.1/tasks(6f358f7d-54a3-e611-816b-000d3a80c8b8)
?OData-EntityId: https://demo.luoyong.me/api/data/v8.1/tasks(6f358f7d-54a3-e611-816b-000d3a80c8b8)
?Access-Control-Expose-Headers: Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
?
?
?--changesetresponse_c5387a7e-c5c6-4ea5-95bd-13df85d67046
?Content-Type: application/http
?Content-Transfer-Encoding: binary
?Content-ID: 3
?
?HTTP/1.1 204 No Content
?OData-Version: 4.0
?Location: https://demo.luoyong.me/api/data/v8.1/ly_tests(6c358f7d-54a3-e611-816b-000d3a80c8b8)
?OData-EntityId: https://demo.luoyong.me/api/data/v8.1/ly_tests(6c358f7d-54a3-e611-816b-000d3a80c8b8)
?Access-Control-Expose-Headers: Preference-Applied,OData-EntityId,Location,ETag,OData-Version,Content-Encoding,Transfer-Encoding,Content-Length,Retry-After
?
?
?--changesetresponse_c5387a7e-c5c6-4ea5-95bd-13df85d67046--
?--batchresponse_8071c8bc-f577-44a6-a246-e27c1ec027e2--
?"

 

如果是查询操作呢,记得查询操作不要放到changeset中,我举个例子如下:

 

请求的内容如下,最后一个batch结尾只需要一个,其余的每个查询的格式是固定的,我这里举了两个查询的例子。

--batch_AAA123
Content-Type: application/http
Content-Transfer-Encoding:binary

GET https://luoyongdemo.crm.dynamics.com/api/data/v9.1/accounts?$select=createdon&$top=1 HTTP/1.1
Accept: application/json

--batch_AAA123
Content-Type: application/http
Content-Transfer-Encoding:binary

GET https://luoyongdemo.crm.dynamics.com/api/data/v9.1/contacts?$select=createdon&$top=1 HTTP/1.1
Accept: application/json

--batch_AAA123--

 

返回内容示例如下:

--batchresponse_169a82f8-26b1-4b42-8496-dd7030665e3e
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{"@odata.context":"https://luoyongdemo.crm.dynamics.com/api/data/v9.1/$metadata#accounts(createdon)","value":[{"@odata.etag":"W/\"665451369\"","createdon":"2020-10-07T09:41:50Z","accountid":"46c66651-8108-eb11-a813-000d3a111c98"}]}
--batchresponse_169a82f8-26b1-4b42-8496-dd7030665e3e
Content-Type: application/http
Content-Transfer-Encoding: binary

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal
OData-Version: 4.0

{"@odata.context":"https://luoyongdemo.crm.dynamics.com/api/data/v9.1/$metadata#contacts(createdon)","value":[{"@odata.etag":"W/\"822799980\"","createdon":"2020-12-04T08:09:29Z","contactid":"dd535807-0836-eb11-a813-000d3a148177"}]}
--batchresponse_169a82f8-26b1-4b42-8496-dd7030665e3e--

 

 

 

posted @ 2019-05-20 21:44  微软MVP(15-18)罗勇  阅读(1074)  评论(0编辑  收藏  举报