我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),这是我的第486篇原创文章,写于2022年10月23日。

Power Fx是开源的,​​Microsoft Power Fx on GitHub​​​ ,目前主要用于Canvas App,也开始逐步用于Model-Driven App的命令栏定制了,还会持续扩展。介绍请参考官网的 ​​Microsoft Power Fx overview​​ 。

对于简单界面和逻辑处理,用到的Power Fx有限,但是对于复杂的界面和批量操作,用表单进行操作(​​EditForm, NewForm, SubmitForm, ResetForm, and ViewForm functions in Power Apps​​)不够用,需要用到一些别的操作我这里验证一些常用的操作。

为了更好的说明,我这里做个场景,就是一个搜索功能,有则展示,没有则新增一行记录,有一个添加按钮可以不断添加记录,保存按钮一次保存所有新增记录。

我先创建一个简单的Screen如下,将搜索代码设置为

ClearCollect(
colTestEntities,
Defaults('Test Entities')
);
Notify($"colTestEntities是否为空:{IsEmpty(colTestEntities)},colTestEntities中记录的行数:{CountRows(colTestEntities)}");
Clear(colTestEntities);
Notify($"colTestEntities是否为空:{IsEmpty(colTestEntities)}");

点击下搜索按钮,这样就可以创建一个名为colTestEntities的Collection。可以看到第一行代码复制了Test Entity这个实体的架构,添加了一行新记录到colTestEntities这个Collection。因为有一行新记录,所以用​​IsEmpty​​​来测试这个colTestEntities,返回的结果是false,​​CountRows​​​返回的结果是1,代表这个Collection中有一行记录。对集合执行​​Clear​​​后再来看,IsEmpty函数的结果就返回true,记住,不要用​​IsBlank​​,用这个函数来测试集合的话,总是返回True。

使用Power Fx进行Microsoft Dataverse相关操作_Power Apps

我们看一下Collection的样子如下,列标题用的是字段的逻辑名称。

使用Power Fx进行Microsoft Dataverse相关操作_Power Apps_02


写代码引用字段名的时候既可以用字段的显示名称,也可以用字段的逻辑名称(大部分字段类型可以使用逻辑名称,但是查找字段类型要使用架构名称,其实我们做项目一般要求字段的架构名称也特意设置为全小写),值得注意的是大部分函数都要求使用字段名称时候直接使用字段名(显示名或者大部分字段可以使用逻辑名,查找字段等少部分类型字段使用架构名)或者用单引号将字段名(显示名或者大部分字段可以使用逻辑名,查找字段等少部分类型字段使用架构名)引用起来(在字段名有空格时候需要用单引号,如果字段名中有单引号就用两个单引号就可以),目前有两个函数就是​​AddColumns​​​ 和 ​​ShowColumns​​ ,要求引用字段名的时候要用双引号。

列是否新记录,我用的表达式是 If(IsBlank(ThisItem.'Test Entity'),"是","否") ,新增记录的主键的值是空的,我用这个来判断是否新记录。

为了展示现有记录,我还需要为其他列使用不同的控件类型,并设置不同的默认值(来自现有记录的值来作为默认值显示)来展示。

Name字段是文本字段,用Text input控件映射即可。

选项集类型字段用Combo box控件来映射,将其Items属性设置为我这个字段使用的全局选项集 Choices('Global Choices') ,并将DefaultSelectedItems设置为 [ThisItem.选项集字段] 。

货币类型字段用Text input控件来影射,将其Default属性设置为 ThisItem.货币字段 。还需要将Format属性设置为Number来防止用户输入非数字,将其Default属性设置为 Text(ThisItem.货币字段, "$ #,###.00") ,我这里对货币的展示使用​​Text​​函数进行了格式化,但是这种格式化的话显示的货币符号都是美元,请注意。如果不将Format设置为Number,而是保持Text不变的话,可以将Default设置为 Text(ThisItem.货币字段, $"{Text(ThisItem.'transactioncurrencyid'.'Currency Code')} #,###.00") 显示货币符号加上货币数值。

查找类型的字段也是用Combo box控件来映射,因为我这个字段是查找到用户(逻辑名为systemuser,显示名为User),所以我设置Item属性的值为 Users (你可能会为什么不用Enabled Users视图?因为要考虑到用户离职后用户被禁用了,如果使用这个视图就会不显示值),并将DefaultSelectedItems设置为ThisItem.ly_submittedby。当然也可以考虑将Items属性的值设置为 If(IsBlank(ThisItem.'Test Entity'),Users,Filter(Users, 'Users (Views)'.'Enabled Users')) ,这样新建记录的时候只能从有效用户的列表中进行选择。

新增记录按钮的OnSelect代码我设置为 Collect(colTestEntities,Defaults('Test Entities')) ,也就是新增一条空记录。

保存记录我用的如下代码,我这里为了简便只保存新建记录,而且要新建记录的主属性设置了值财保存,保存记录我们用​​Patch​​​函数,如果是保存新增记录,Patch函数的第二个参数是Defaults开头的要保存表的显示名称,可以看到我用来设置字段的值,分别使用了字段逻辑名称和显示名称来指定要设置的字段的值,都是可以的(大部分字段可以使用逻辑名,查找字段等少部分类型字段使用架构名)。我也进行验证了,对于货币类型字段不需要特别处理,直接用​​Value​​将字段中的值转变为数字就可以,那怕当前用户没有在Model-Driven App中设置默认货币也是可以保存成功的,这时候用的应该是环境建立时候设置的基础货币,如果用户设置了自己的偏好货币,保存时候设置的当前记录的币种就是用户的偏好货币。

用户在Model-Driven App中设置偏好货币的截图:

使用Power Fx进行Microsoft Dataverse相关操作_Canvas App_03

ForAll(
Filter(
galTestEntities.AllItems,
!IsBlank(txtName.Text) && lblIsNewRecord.Text = "是"
),
Patch(
'Test Entities',
Defaults('Test Entities'),
{
'ly_name': txtName.Text,
'ly_choice': cmbChoice.Selected.Value,
'ly_money': Value(txtMoney.Text),
最近审批提交人: cmbSubmittedBy.Selected
}
)
);
Select(btnSearch);
Notify(
"保存成功,已经按照当前搜索条件刷新了!",
NotificationType.Success
);

你可能会问,这是一行一行插入,我可以用一次函数调用插入多行记录吗?答案是可以的,使用Patch函数可以,请参考官方文档 ​​Create or update bulk records in Power Apps​​ ,我这里简单改写代码后如下:

ClearCollect(colTestEntitesforCreate,Defaults('Test Entities'));
Clear(colTestEntitesforCreate);
ForAll(
Filter(
galTestEntities.AllItems,
!IsBlank(txtName.Text) && lblIsNewRecord.Text = "是"
),
Patch(
colTestEntitesforCreate,
Defaults('Test Entities'),
{
ly_name: txtName.Text,
ly_choice: cmbChoice.Selected.Value,
ly_money: Value(txtMoney.Text),
最近审批提交人: cmbSubmittedBy.Selected
}
)
);
Patch('Test Entities',colTestEntitesforCreate);
Select(btnSearch);
Notify(
"保存成功,已经按照当前搜索条件刷新了!",
NotificationType.Success
);


我抓取网络包可以看到,他是每条新增的记录发起一个HTTP POST请求来新增,我也做了测试,如果这插入2条记录,第一条失败,第二条依然是可以成功插入的。

上面的代码,我如果将第19行的Patch改成​​Collect​​的话也是可以一次插入多条记录的,但是稍有不同的是,发起的是一个batch请求,一个请求插入两条记录,如下:

使用Power Fx进行Microsoft Dataverse相关操作_Power Apps_04

但是也据我观察,如果第一条失败(比如文本字段输入的字符超过允许的最多字符,货币字段输入的数字大于允许的最大数字),第二条也是可以插入的,这个时候只发起一次请求,也只有为能正确插入的那条记录发起一次HTTP请求了。

前面的代码借助了一个辅助的Collection,那么不借助辅助的Collection可以吗?也可以,如下:

ForAll(
Filter(
galTestEntities.AllItems,
!IsBlank(txtName.Text) && lblIsNewRecord.Text = "是"
),
Patch(
colTestEntities,
LookUp(
colTestEntities,
IsBlank('Test Entity') && IsBlank(ly_name)
),
{
ly_name: txtName.Text,
ly_choice: cmbChoice.Selected.Value,
ly_money: Value(txtMoney.Text),
最近审批提交人: cmbSubmittedBy.Selected
}
)
);
Patch(
'Test Entities',
Filter(
colTestEntities,
IsBlank('Test Entity') && !IsBlank(ly_name)
)
);
Select(btnSearch);
Notify(
"保存成功,已经按照当前搜索条件刷新了!",
NotificationType.Success
);


还有前面的代码只插入记录,可以同步更新吗?那这需要找到主键,我需要在Gallery中增加显示主键,比如我命名这个Label为lblKey,设置其Text为ThisItem.'Test Entity' ,Visible我设置为false,然后使用类似如下代码就可以,从这也可以看出,使用Patch批量处理数据时候,既可以更新,也可以插入,可以同时在一批里面。值得注意的是RemoveIf这行不能少,否则会额外插入新增的空记录,但是这些空记录的插入会很可能会失败的(比如表的主属性一般要求是必须输入,但是插入记录时候为空值会导致报错而导致插入记录失败)。

ForAll(
galTestEntities.AllItems,
If(
IsBlank(lblKey.Text),
Patch(
colTestEntities,
LookUp(
colTestEntities,
IsBlank('Test Entity') && IsBlank(ly_name)
),
{
ly_name: txtName.Text,
ly_choice: cmbChoice.Selected.Value,
ly_money: Value(txtMoney.Text),
最近审批提交人: cmbSubmittedBy.Selected
}
),
Patch(
colTestEntities,
LookUp(
colTestEntities,
'Test Entity' = GUID(lblKey.Text)
),
{
ly_name: txtName.Text,
ly_choice: cmbChoice.Selected.Value,
ly_money: Value(txtMoney.Text),
最近审批提交人: cmbSubmittedBy.Selected
}
)
)
);
RemoveIf(colTestEntities,IsBlank('Test Entity') && IsBlank(ly_name));
Patch(
'Test Entities',
colTestEntities
);
Select(btnSearch);
Notify(
"保存成功,已经按照当前搜索条件刷新了!",
NotificationType.Success
);