无依赖简单易用的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及其后面部分,
再将其中的 ", <, > 三个符号分别替换掉作为",<,> 后作为分页查询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 <fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'> 34 <entity name='contact'> 35 <attribute name='contactid' /> 36 <filter type='and'> 37 <condition attribute='statecode' operator='eq' value='0' /> 38 </filter> 39 </entity> 40 </fetch> 41 </textarea> 42 <br /> 43 <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, '"').replace(/</g, '<').replace(/>/g, '>'); 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秒,所以记录数很多的时候请耐心点),计算下总数。
当然,有个注意的地方,查询是以当前用户身份去做的,结果集也是当前用户能看到的结果集,因为权限控制,部分记录当前用户可能看不到,那么和真正的所有的符合条件的所有记录数(当前用户看到的,看不到的所有的)可能有差别,因为这个结果集只包括了当前用户有读取权限的结果集,那些没有读取权限的记录不会被统计进去。
当然,你如果用系统管理员角色用户来执行的,统计的就是真正的所有的符合条件的记录集。