天天看点

通过Web API分页查询数据并通过批量操作删除提升效率

我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复458或者20211112可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!

我们知道一次查询最多返回Web API 5000条记录,删除记录的时候一条一条删除的话就比较慢,我们可以采用Web API的批量操作来加速。今天我们来举个例子,就是一个父子关系的实体,我在父实体的表单上添加一个【删除所有子记录】的按钮,点击就删除它所有的子项目。

按钮我已经做好了,如下,这个我就不讲解如何做的了,请参考我前面的博文:​​Dynamics 365定制:在实体的列表界面添加按钮​​​ ,​​Dynamics 365 CE命令栏按钮点击后刷新表单页面方法​​ 等。

通过Web API分页查询数据并通过批量操作删除提升效率

具体的RibbonDiff文件供参考如下:

<RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="ly.ly_demoentity.DeleteAllSubEntities.Button.CustomAction" Location="Mscrm.Form.ly_demoentity.MainTab.Save.Controls._children" Sequence="75">
            <CommandUIDefinition>
              <Button Command="ly.ly_demoentity.DeleteAllSubEntities.Command" Id="ly.ly_demoentity.DeleteAllSubEntities.Button" LabelText="$LocLabels:ly.ly_demoentity.DeleteAllSubEntities.Button.LabelText" Sequence="75" TemplateAlias="o2" ModernImage="Remove" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>
        <Templates>
          <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
        </Templates>
        <CommandDefinitions>
          <CommandDefinition Id="ly.ly_demoentity.DeleteAllSubEntities.Command">
            <EnableRules />
            <DisplayRules>
              <DisplayRule Id="ly.ly_demoentity.DeleteAllSubEntities.DisplayRule" />
            </DisplayRules>
            <Actions>
              <JavaScriptFunction FunctionName="LuoYong.DemoSubEntity.DeleteAllSubEntitiesAction" Library="$webresource:ly_/scripts/demosubentity/demosubentity.js">
                <CrmParameter Value="PrimaryControl" />
              </JavaScriptFunction>
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>
        <RuleDefinitions>
          <TabDisplayRules />
          <DisplayRules>
            <DisplayRule Id="ly.ly_demoentity.DeleteAllSubEntities.DisplayRule">
              <EntityPrivilegeRule PrivilegeType="Delete" PrivilegeDepth="Basic" EntityName="ly_demosubentity" Default="false" InvertResult="false" />
            </DisplayRule>
          </DisplayRules>
          <EnableRules />
        </RuleDefinitions>
        <LocLabels>
          <LocLabel Id="ly.ly_demoentity.DeleteAllSubEntities.Button.LabelText">
            <Titles>
              <Title description="删除所有子记录" languagecode="1033" />
            </Titles>
          </LocLabel>
        </LocLabels>
      </RibbonDiffXml>      

通过Web API Batch来执行操作可以参考我的博文:​​Dynamics 365为子网格添加按钮并获取选择记录的信息​​​ ,​​使用JS通过Web API执行批量操作,多个操作是一个事务​​​ ,官方文档可以参考 ​​Execute batch operations using the Web API​​​ 。通过Web API 分页查询数据可以参考我的博文:​​Dynamics 365 Web API分页查询数据​​​ ,官方文档可以参考:​​Web API Query Data Sample​​ 。

不多说,直接上代码如下:

"use strict";
var LuoYong = window.LuoYong || {};
LuoYong.DemoSubEntity = LuoYong.DemoSubEntity || {};
(function () {
    var RetrieveAllRecords = function (clientUrl,queryString,recordsPerPage) {
        let lsEntities = [];
        let nextLink = `${clientUrl}/api/data/v9.2/${queryString}`;
        if (!recordsPerPage || recordsPerPage > 5000) {
            recordsPerPage = 5000;
        }
        var req = new XMLHttpRequest();
        while (nextLink !== "") {
            req.open("GET", nextLink, false);
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.setRequestHeader("Prefer", `odata.maxpagesize=${recordsPerPage}`);
            req.onreadystatechange = function () {
                if (this.readyState == 4) {
                    req.onreadystatechange = null;
                    if (this.status == 200) {
                        var responseJSON = JSON.parse(this.responseText);
                        if (responseJSON["@odata.nextLink"]) {
                            nextLink = responseJSON["@odata.nextLink"];
                        }
                        else {
                            nextLink = "";
                        }
                        if (responseJSON.value != null) {
                            lsEntities = lsEntities.concat(responseJSON.value);
                        }
                    }
                    else {
                        nextLink = "";
                        var error = JSON.parse(this.responseText).error;
                        Xrm.Navigation.openErrorDialog({ message: error.message });
                    }
                }
            };
            req.send();
        }
        return lsEntities;
    }

    this.DeleteAllSubEntitiesAction = function (primaryControl) {
        try {
            Xrm.Utility.showProgressIndicator("处理中,请稍候...");
            var formContext = primaryControl;
            var deletedCount = 0;
            var clientUrl = Xrm.Utility.getGlobalContext().getClientUrl();
            var demoSubEntities = RetrieveAllRecords(clientUrl, `ly_demosubentities?$select=ly_demosubentityid&$filter=_ly_demoentityid_value eq '${formContext.data.entity.getId().replace('{', '').replace('}', '')}'&$orderby=createdon`,5000);
            var recordsCount = demoSubEntities.length;
            console.log(`总计需要处理的记录数是${recordsCount}`);
            var requestMsg = [];
            var batchId = Date.now();
            for (var i = 0; i < recordsCount; i++) {
                requestMsg.push(`--batch_${batchId}`);
                requestMsg.push("Content-Type: application/http");
                requestMsg.push("Content-Transfer-Encoding: binary");
                requestMsg.push("");
                requestMsg.push(`DELETE ${clientUrl}/api/data/v9.2/ly_demosubentities(${demoSubEntities[i].ly_demosubentityid}) HTTP/1.1`);
                requestMsg.push("");
                if (recordsCount === 1 || (i >= 1 && (i % 999 === 0 || i === recordsCount - 1))) {//一个batch最多1000个请求
                    requestMsg.push(`--batch_${batchId}--`);
                    var req = new XMLHttpRequest()
                    req.open("POST", `${clientUrl}/api/data/v9.2/$batch`, false);//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) {
                            req.onreadystatechange = null;
                            var regexp = RegExp('204 No Content', 'g');
                            //if (this.status == 200) {
                            //    console.log(this.responseText);                               
                            //}
                            //else {
                            //    Xrm.Navigation.openErrorDialog({ message: this.responseText });
                            //}
                            console.log(this.responseText);
                            var matches = this.responseText.matchAll(regexp);
                            deletedCount += Array.from(matches, m => m[0]).length;
                            console.log(`本批次成功删除记录${Array.from(matches, m => m[0]).length}条。`);
                        }
                    };
                    req.send(requestMsg.join("\n"));
                    requestMsg = [];
                }
            }
            Xrm.Utility.closeProgressIndicator();
            var confirmStrings = { text: `总计要删除${recordsCount}条记录,实际删除${deletedCount}条记录!`, title: "提示信息" };
            var confirmOptions = { height: 200, width: 450 };
            Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions);
        }
        catch (ex) {
            Xrm.Navigation.openErrorDialog({ message: ex.message });
            Xrm.Utility.closeProgressIndicator();
        }
    };
}).call(LuoYong.DemoSubEntity);      
  1. 一个Batch请求里面默认最多包括1000个请求,超过了会报错;
  2. 同一时刻只能有一个Batch请求运行,所以我上面代码执行批量删除的时候发起的是同步请求而不是异步请求;
  3. 上面示例代码写法是参考删除删除子网格多条记录而做的,一个Batch请求中如果有一条记录删除报错,会影响后面的记录删除,而且这个batch请求返回的http status也不是200,应该是400. 统计删除记录成功数量可以参考我得方法,看batch返回文本中 204 No Content 的数量。
  4. 这个Batch请求中没有用到changeset,代码写起来更加简单,这个Batch中所有请求并不在一个事务中。