我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),这是我的第474篇文章,写于2022年0810日。
在Canvas App中进行数据搜索,如果涉及到的条件比较复杂或者表达式中不好写,这时候如果能利用Microsoft Dataverse的公共视图,很多时候会收到不错的效果。更别提可以通过编程方式修改公共视图的筛选条件,达到更好的效果。这些包括但是不限于如下的做法:
- 实体关联起来查询可以不通过关系(Lookup字段)吗?
- 使用FetchXml的左外连接功能实现查询没有子记录的父记录功能
- Dynamics 365层级数据的定义、展示与查询
- FetchXml跨实体使用或者操作符(or)示例
简单来说更新公共视图的Fetchxml就是执行类似如下的代码,然后发布下就可以了。
var data =
{
"fetchxml": `<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>
<entity name='account'>
<attribute name='name' />
<attribute name='primarycontactid' />
<attribute name='telephone1' />
<attribute name='accountid' />
<attribute name='createdon' />
<order attribute='createdon' descending='false' />
<link-entity name='contact' from='parentcustomerid' to='accountid' link-type='outer' alias='aa'>
</link-entity>
<filter type='and'>
<condition attribute='contactid' operator='null' entityname='contact' />
</filter>
</entity>
</fetch>`
}
Xrm.WebApi.updateRecord("savedquery", "a6f3328c-3bfc-eb11-94ef-00224816655e", data).then(
function success(result) {
console.log("Public view updated");
},
function (error) {
console.log(error.message);
}
);
首先是最简单的使用方法,也就是在Gallery控件中使用视图,如下图:
将Gallery控件的Data source设置为Microsoft Dataverse中的表,Views设置为对应的View即可。
可以看到这样设置后Gallery的Items属性变成了 Filter(Accounts, 'Accounts (Views)'.'My Active Accounts') 这种。可以看到是表的复数名称 + 空格 + (Views)后机上点,加上视图的名称。
如果要做成一个搜索效果呢,我这里将这个Gallery的表达式改成:
SortByColumns(Search(Filter(Accounts, 'Accounts (Views)'.'My Active Accounts'),TextSearchBox1_1.Text,"accountnumber","name"),"name",Ascending)
发布后利用Monitor功能,我搜索 A da看看到结果如下:
可以看到请求的信息是如下:
{
"url": "https://thomasluodemo.crm5.dynamics.com/api/data/v9.0/accounts?%24filter=%28%28contains%28name%2C%27A+da%27%29%29or%28contains%28accountnumber%2C%27A+da%27%29%29%29&%24orderby=name+asc&%24select=accountid%2Caccountnumber%2Cname%2Centityimage%2Ctelephone1%2Centityimage_timestamp&savedQuery=00000000-0000-0000-00aa-000010001001",
"method": "GET",
"headers": {
"Accept": "application/json",
"Prefer": "odata.maxpagesize=500,odata.include-annotatinotallow=*",
"BatchInfo": {
"baseUrl": "https://thomasluodemo.crm5.dynamics.com/api/data/v9.0",
"headers": {
"Accept": "application/json",
"Prefer": "odata.maxpagesize=500,odata.include-annotatinotallow=*"
},
"batchId": ""
},
"x-ms-pa-client-custom-headers-options": {
"addCustomHeaders": true
},
"x-ms-pa-client-telemetry-options": "paclient-telemetry {\"operationName\":\"ODataConnector.getRowsAsync\"}"
},
"body": ""
}
可以看到请求的url: accounts?orderby=name+asc&$select=accountid,accountnumber,name,entityimage,telephone1,entityimage_timestamp&savedQuery=00000000-0000-0000-00aa-000010001001 是比较优化,filter写得不错,select也只获取少部分列,而不是所优列。
如果我将记录Deactivate或者将记录分派给别人,这样就搜索不到了,搜索条件是叠加在视图的搜索条件之上,两者的关系是 and 的关系。
下面我们来看看纯代码演示,我做了三个按钮如下图:
分别执行的代码是:
ClearCollect(colAccounts,ShowColumns(Filter(Accounts,'Accounts (Views)'.'My Active Accounts',StartsWith(ThisRecord.'Account Name',"A da")),"name"))
UpdateContext(
{
Countofaccount: CountIf(
Filter(
Accounts,
'Accounts (Views)'.'My Active Accounts'
),
StartsWith(
ThisRecord.'Account Name',
"A da"
)
)
}
)
UpdateContext(
{
CountofaccountusingCountRows: CountRows(
Filter(
Accounts,
'Accounts (Views)'.'My Active Accounts',
StartsWith(
ThisRecord.'Account Name',
"A da"
)
)
)
}
)
这三个按钮点击后执行的操作如下图:
第一个执行的Operation是getRows,发起的请求是 https://thomasluodemo.crm5.dynamics.com/api/data/v9.0/accounts?$filter=(startswith(name,'A+da'))&$select=accountid,name&savedQuery=00000000-0000-0000-00aa-000010001001 ,符合预期,返回的结果也是对的,只有1条记录。
第二个使用的是CountIf,Operation是getScalarResult, 发起的请求是 https://thomasluodemo.crm5.dynamics.com/api/data/v9.0/accounts?$apply=filter((startswith(name,'A+da')))/aggregate($count+as+result) ,返回的结果是3条,为啥结果错了,从生成的请求来看,是没有使用view的筛选条件导致导致的,这可能是个bug吧。
第三个使用的Count,operation是CountRows,发起的请求内容如下,并没有记录发起的Web API请求url是啥,但是返回结果是正确的,还告诉我没有没有代理,返回结果可能有问题。
我通过抓取网络包获取到了请求的内容是:accounts?select=accountid,name&savedQuery=00000000-0000-0000-00aa-000010001001,Prefer: odata.maxpagesize=500,odata.include-annotatinotallow=*
Prefer: odata.maxpagesize=500,odata.include-annotatinotallow=*
从抓取的网络包来看,如果满足条件的记录数超过500行(这个参数最大可以改到2000)的话返回的结果是不能信任的,那就可能是错误的。
{
"status": null,
"duration": null,
"dataSource": "Accounts",
"responseSize": null,
"controlName": "Button4_2",
"propertyName": "OnSelect",
"nodeId": 11,
"formulaData": {
"script": "",
"spanStart": null,
"spanEnd": null
},
"data": {
"context": {
"entityName": "Button4_2",
"propertyName": "OnSelect",
"nodeId": 11,
"id": 514,
"diagnosticContext": {
"dataOperation": {
"protocol": "local",
"operation": "countRows",
"dataSource": "Accounts"
}
}
},
"info": "Formula not delegated. 1 rows scanned.",
"helpUrl": "https://aka.ms/pamonitordelegation",
"issue": "This function can be delegated but the selection formula used prevented the datasource from processing the query.It might not work correctly on large data sets and an incorrect result might be returned.The data row limit for non-delegable queries defined in the app settings is 500."
}
}
如果我直接使用如下的CountRows,也就是直接看视图有多少行记录,不像前面的第三个按钮加筛选条件,执行的Operation和发起的请求 (accounts?$select=accountid,accountnumber,name,accountcategorycode,creditlimit,entityimage,telephone1,websiteurl,primarycontactid,ly_datetzindependent,ly_dateandtimetzindependent,ly_dateuserlocal,ly_dateandtimeuserlocal,ly_datedateonly&savedQuery=00000000-0000-0000-00aa-000010001001)和第三个按钮差不多。
UpdateContext(
{
CountofaccountusingCountRows: CountRows(
Filter(
Accounts,
'Accounts (Views)'.'My Active Accounts'
)
)
}
)
从上面可以看到,在使用Count和CountIf配合View的时候没有代理,结果集比较大的时候不可靠。
但是如果是CountIf不配合View的话是可以用代理的,数据准确性会大大增加(默认符合条件的结果集不超过5万行都是准确的)。
如果我一个按钮点击执行的代码是:
UpdateContext(
{
Countofaccount: CountIf(
Accounts,
StartsWith(
ThisRecord.'Account Name',
"A da"
)
)
}
)
然后我通过Monitor看到,这个操作执行的Operation是 getScalarResult ,发起请求如下,请求的url是:https://thomasluodemo.crm5.dynamics.com/api/data/v9.0/accounts?count+as+result).
{
"url": "https://thomasluodemo.crm5.dynamics.com/api/data/v9.0/accounts?%24apply=filter%28startswith%28name%2C%27A+da%27%29%29%2Faggregate%28%24count+as+result%29",
"method": "GET",
"headers": {
"Accept": "application/json",
"x-ms-pa-client-custom-headers-options": {
"addCustomHeaders": true
},
"BatchInfo": {
"baseUrl": "https://thomasluodemo.crm5.dynamics.com/api/data/v9.0",
"headers": {
"Accept": "application/json"
},
"batchId": ""
},
"x-ms-pa-client-telemetry-options": "paclient-telemetry {\"operationName\":\"ODataConnector.getScalarResultCoreAsync\"}"
},
"body": ""
}