无依赖简单易用的Dynamics 365实体记录数计数器并能计算出FetchXml返回的记录数

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

我们知道Dynamics 365 Customer Engagement中当返回的结果集超过5000的时候就不会显示具体多少条,如果我想知道有多少条呢?有时候我还需要知道实体的记录数到底有多少?

怎么办?每次写个程序去查询具体有多少?普通用户不方便(可能需要安装比较新版本的.Net Framework),去数据库查询?麻烦且需要转换FetchXml的条件,去数据库查询还不能反映出要执行这个用户的权限能看到的记录数,所以可能得出错误的结论。

如果,有一个页面,你贴进去FetchXml,这个FetchXml可以来自高级查找下载的FetchXml,也有默认值,自己改下实体名和主键字段的名称,就可以帮我算出来到底有多少记录,那就好了。

如果你这么想,Follow me。

我的程序做法是先将这个最初的FetchXml(这个FetchXml最大可能来自高级查找中的下载 Fetch XML按钮,如果纯粹是计数的话,我已经写好模板了,不用去下载了)转换成执行聚合计算的FetchXml,通过Web API执行这个FetchXml,如果不报错,就是说没有超过5万行记录(AggregateQueryRecordLimit参数值默认五万),直接通过聚合计算出来,效率很好,比将记录获取过来计数快。

如果报错,则没有办法了,只有分页查询来计数了。将第一次获取到的记录集中的pagingcookie进行两次unescape,然后将其中的页码解析出来(我的程序是用正则表达式RegExp解析出来),

然后继续循环执行新的分页查询FetchXml,将前面解析出来的页码加1后作为这个分页查询FetchXml中的page属性值,原来的pagingCookie去掉istracking及其后面部分,

再将其中的 ", <, > 三个符号分别替换掉作为&quot;,&lt;,&gt; 后作为分页查询FetchXml的paging-cookie属性值,不断执行查询并计数,直到查询到没有更多结果为止。

 

下面是我建立的网页(HTML)类型的Web 资源 ly_/common/page/CountEntityRecords.htm 的源码:

  1 <!DOCTYPE html>
  2 <html>
  3 <head>
  4     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5     <meta http-equiv="X-UA-Compatible" content="IE=edge" charset="utf-8">
  6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7     <title>获取实体记录数_罗勇原创_www.luoyong.me</title>
  8     <style type="text/css">
  9         table {
 10             border-collapse: collapse;
 11             width: 100%;
 12         }
 13 
 14         table, td, th {
 15             border: 1px solid black;
 16         }
 17 
 18         tr:nth-of-type(odd) {
 19             background: #fff;
 20         }
 21 
 22         tr:nth-of-type(even) {
 23             background: #F5FAFA;
 24         }
 25 
 26         table tr:hover td {
 27             background-color: #ffff99;
 28         }
 29     </style>
 30 </head>
 31 <body>
 32     <textarea id="fetchxml" cols="120" rows="15">
 33 &lt;fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'&gt;
 34 &nbsp;&lt;entity name='contact'&gt;
 35 &nbsp;&nbsp;&lt;attribute name='contactid' /&gt;
 36 &nbsp;&nbsp;&lt;filter type='and'&gt;
 37 &nbsp;&nbsp;&nbsp;&lt;condition attribute='statecode' operator='eq' value='0' /&gt;
 38 &nbsp;&nbsp;&lt;/filter&gt;
 39 &nbsp;&lt;/entity&gt;
 40 &lt;/fetch&gt;
 41     </textarea>
 42     <br />
 43     &nbsp;&nbsp;&nbsp;&nbsp;<button id="convertit">查看记录数</button>
 44     <br />
 45     <hr />
 46     <textarea id="countResult" cols="120" rows="2" placeholder="这里显示超过AggregateQueryRecordLimit参数值(默认五万)的计数结果."></textarea>
 47     <br />
 48     <hr />
 49     <script src="../../../ClientGlobalContext.js.aspx" type="text/javascript"></script>
 50     <script type="text/javascript">
 51         var crmclientcontext =null;
 52         var clientUrl ="";
 53         var entitySetName="";
 54         var orgFetchXML = "";
 55         var pagingFetchXML = "";
 56         var aggFetchXML ="";
 57         var recordCount = 0;
 58 
 59         function getPagingCookie(pagingCookie) {
 60             var pagingInfo = {};
 61             try {
 62                 pagingCookie = unescape(unescape(pagingCookie));
 63                 //console.log(pagingCookie);
 64                 pagingCookie = pagingCookie.substring(pagingCookie.indexOf("pagingcookie") + 14, pagingCookie.indexOf("istracking") - 1);
 65                 pagingInfo.nextPageNum = parseInt(pagingCookie.match(/\d+/)[0]) + 1;
 66                 pagingCookie = pagingCookie.replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
 67                 pagingCookie = pagingCookie.substring(0, pagingCookie.lastIndexOf('%'));
 68                 pagingInfo.pagingCookie = pagingCookie;                
 69             } catch (e) {
 70                 throw new Error(e);
 71             } 
 72             return pagingInfo;
 73         }
 74 
 75         function executeFetchXML(entitySetName, fetchXML, successCallback, errorCallback) {
 76             var req = new XMLHttpRequest();
 77             req.open("GET", clientUrl + entitySetName + "?fetchXml=" + encodeURI(fetchXML), false);
 78             req.setRequestHeader("Accept", "application/json");
 79             req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 80             req.setRequestHeader("OData-MaxVersion", "4.0");
 81             req.setRequestHeader("OData-Version", "4.0");
 82             req.onreadystatechange = function () {
 83                 if (this.readyState == 4 /* complete */) {
 84                     req.onreadystatechange = null;
 85                     if (this.status == 200) {
 86                         successCallback(JSON.parse(this.responseText));
 87                     }
 88                     else {
 89                         errorCallback(JSON.parse(this.responseText));
 90                     }
 91                 }
 92             }
 93             req.send();
 94         }
 95 
 96         function errorCallback(errorJson) {
 97             console.log(errorJson);
 98             if (typeof errorJson.error.message === "string") {
 99                 Xrm.Utility.alertDialog("执行FetchXml出错!" + errorJson.error.message);
100             }
101             else {
102                 Xrm.Utility.alertDialog("执行FetchXml出错,请查看console中的输出信息!");
103             }
104         }
105 
106         //当执行聚合FetchXml失败,原因是因为超过AggregateQueryRecordLimit参数值的时候执行的函数
107         function errorCallbackHandleAggregateQueryRecordLimit(errorJson) {
108             console.log(errorJson);
109             if (typeof errorJson.error.message === "string") {
110                 if (errorJson.error.message.indexOf('AggregateQueryRecordLimit') >= 0) {
111                     console.log("记录数超过了AggregateQueryRecordLimit参数的值(默认五万),将执行分页查询来计数.");
112                     executeFetchXML(entitySetName, orgFetchXML, successCallback, errorCallback);
113                 }
114                 else{
115                     Xrm.Utility.alertDialog("执行FetchXml出错!" + errorJson.error.message);
116                 }
117             }
118             else {
119                 Xrm.Utility.alertDialog("执行FetchXml出错,请查看console中的输出信息!");
120             }
121         }
122 
123         //分页查询调用成功的执行函数
124         function successCallback(responseJson) {
125             if (responseJson.value.length >= 1) {
126                 recordCount += responseJson.value.length;
127                 console.log(recordCount + " : " + new Date());
128             }
129             if (responseJson.hasOwnProperty("@Microsoft.Dynamics.CRM.fetchxmlpagingcookie")) {
130                 var pageInfo = getPagingCookie(responseJson["@Microsoft.Dynamics.CRM.fetchxmlpagingcookie"]);
131                 pagingFetchXML = orgFetchXML.replace("<fetch", "<fetch page='" + pageInfo.nextPageNum + "' paging-cookie='" + pageInfo.pagingCookie + "'");
132                 //console.log(pagingFetchXML);
133                 executeFetchXML(entitySetName, pagingFetchXML, successCallback, errorCallback);
134             }
135             else {
136                 console.log("分页查询完毕啦!" + " : " + new Date());
137                 document.getElementById('countResult').innerText = "总记录条数是" + recordCount;
138                 var tables = document.getElementsByTagName('table');
139                 for (var i = 0; i < tables.length; i++) {
140                     tables[i].parentNode.removeChild(tables[i]);
141                 }
142             }
143         }
144 
145         //一个聚合函数可以查询出记录结果的时候显示方式
146         function successCallbackToShowData(responseJson) {
147             document.getElementById('countResult').innerText = "罗勇(www.luoyong.me)原创的程序执行完毕,结果见下表:";
148             var tables = document.getElementsByTagName('table');
149             for (var i = 0; i < tables.length; i++) {
150                 tables[i].parentNode.removeChild(tables[i]);
151             }
152             if (responseJson.value.length >= 1) {
153                 var recordCount = responseJson.value.length;
154                 var cols = [];
155                 var body = document.getElementsByTagName('body')[0];
156                 var tbl = document.createElement('table');
157                 var tbdy = document.createElement('tbody');
158                 for (var i = 0; i < recordCount; i++) {
159                     for (var propkey in responseJson.value[i]) {
160                         if (propkey != "@odata.etag" && cols.indexOf(propkey) <= -1) {
161                             cols.push(propkey);
162                         }
163                     }
164                 }
165                 cols.sort();
166                 var tr = document.createElement("tr");
167                 var td = document.createElement('td');
168                 td.appendChild(document.createTextNode("序号"));
169                 tr.appendChild(td);
170                 cols.forEach(function (ele) {
171                     td = document.createElement('td');
172                     td.appendChild(document.createTextNode(ele));
173                     tr.appendChild(td);
174                 });
175                 tbdy.appendChild(tr);
176                 for (var i = 0; i < recordCount; i++) {
177                     var tr = document.createElement('tr');
178                     var td = document.createElement('td');
179                     td.appendChild(document.createTextNode((i + 1).toString()));
180                     tr.appendChild(td);
181                     cols.forEach(function (ele) {
182                         if (responseJson.value[i].hasOwnProperty(ele)) {
183                             td = document.createElement('td');
184                             td.appendChild(document.createTextNode(responseJson.value[i][ele]));
185                             tr.appendChild(td);
186                         }
187                         else {
188                             td = document.createElement('td');
189                             tr.appendChild(td);
190                         }
191                     });
192                     tbdy.appendChild(tr);
193                 }
194                 tbl.appendChild(tbdy);
195                 body.appendChild(tbl);
196             }
197         }
198 
199         document.getElementById('convertit').onclick = function () {
200             document.getElementById('countResult').innerText = "罗勇(www.luoyong.me)原创的程序开始执行计数啦,请稍等...";
201             var tables = document.getElementsByTagName('table');
202             for (var i = 0; i < tables.length; i++) {
203                 tables[i].parentNode.removeChild(tables[i]);
204             }
205             if (typeof GetGlobalContext != "undefined") {
206                 crmclientcontext = GetGlobalContext();
207             } else {
208                 throw new Error("引入ClientGlobalContext.js.aspx失败,请检查!");
209             }
210             clientUrl = crmclientcontext.getClientUrl() + "/api/data/v8.2/";
211             orgFetchXML = document.getElementById("fetchxml").value.replace(/"/gm, "'");
212             var entityLogicalName = orgFetchXML.match(/<entity\s?name\s?=\s?\'\w+\'/ig)[0].match(/\'.+\'/i)[0].replace(/\'/g, "");
213             var req = new XMLHttpRequest();
214             req.open("GET", clientUrl + "EntityDefinitions?$select=EntitySetName&$filter=LogicalName" + encodeURI(" eq '" + entityLogicalName + "'"), false);
215             req.setRequestHeader("Accept", "application/json");
216             req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
217             req.setRequestHeader("OData-MaxVersion", "4.0");
218             req.setRequestHeader("OData-Version", "4.0");
219             req.onreadystatechange = function () {
220                 if (this.readyState == 4 /* complete */) {
221                     req.onreadystatechange = null;
222                     if (this.status == 200) {
223                         entitySetName = JSON.parse(this.responseText).value[0].EntitySetName;
224                     }
225                     else {
226                         errorCallback(JSON.parse(this.responseText));
227                     }
228                 }
229             };
230             req.send();
231             aggFetchXML = orgFetchXML.replace("<fetch", "<fetch aggregate='true'");
232             var attrAt = aggFetchXML.search(/<attribute/g);
233             aggFetchXML = aggFetchXML.substring(0, attrAt + 10) + " aggregate='count' alias='recordscount'" + aggFetchXML.substring(attrAt + 10);
234             console.log("根据最初FetchXml改造后的聚合FetchXml =" + aggFetchXML);
235             executeFetchXML(entitySetName, aggFetchXML, successCallbackToShowData, errorCallbackHandleAggregateQueryRecordLimit);
236         };
237     </script>
238 </body>
239 </html>

 

下面我们来看看效果,首先看不足5万行记录结果如下,查询的是某个实体的状态为有效(statecode=0)的记录数,当然你要查看所有记录数,将filter元素的内容手动删除再执行查询即可。

再来看一个超过5万行记录的结果,计数结果为66152行,正确。从日志中可以看到,首先执行聚合查询的时候报错了,然后就分页查询,一次查询出最多5000行(一次返回5000行记录大概耗时10秒,所以记录数很多的时候请耐心点),计算下总数。

 

 当然,有个注意的地方,查询是以当前用户身份去做的,结果集也是当前用户能看到的结果集,因为权限控制,部分记录当前用户可能看不到,那么和真正的所有的符合条件的所有记录数(当前用户看到的,看不到的所有的)可能有差别,因为这个结果集只包括了当前用户有读取权限的结果集,那些没有读取权限的记录不会被统计进去。

当然,你如果用系统管理员角色用户来执行的,统计的就是真正的所有的符合条件的记录集。

posted @ 2018-08-12 14:41  微软MVP(15-18)罗勇  阅读(1081)  评论(0编辑  收藏  举报