资讯动态

Dynamics 365 lookup 校验的两个深坑:MRU 绕过过滤、$select 引用名取值

发布时间:2026/10/15 7:55:16 来源:尧图企业网站定制
一、需求与翻车现场业务需求听起来很简单市场活动方案在选择市场活动计划时只能选到申请人自己名下的计划选别人的要拦下来。第一版实现也很标准给 lookup 控件挂addPreSearch拼 FetchXml 按申请人过滤var fetchXml fetch version1.0 mappinglogical entity namenew_campain filter typeand condition attributenew_applyid operatoreq value applyUserId / condition attributestatecode operatoreq value0 / /filter/entity/fetch; compainCtrl.addPreSearch(function () { compainCtrl.addCustomFilter(fetchXml); });测试却反馈还能选到别人的计划。排查后发现问题出在 lookup 弹窗里一个很容易被忽略的区块。二、坑一MRU最近的记录不走过滤D365 旧版 Web 客户端Xrm.Page 时代的 lookup 弹窗结构上分两个区块查找记录区用户点放大镜搜索结果走服务端 FetchXml 查询——这个区域受 addPreSearch 过滤最近的记录MRUMost Recently Used区客户端本地缓存的快选区显示操作人最近操作过的记录——这个区域直接渲染缓存不经过任何过滤链。所以操作人最近刚操作过别人的计划那条记录就躺在 MRU 里一点就选上了preSearch 形同虚设。更麻烦的是旧版客户端没有禁用 MRU 的官方 API这个口子堵不上。解法只能是双保险——字段 onChange 时反查校验不通过就清空 提示// onChange 反查校验不通过就清空 提示 function validatePlanApplicant(planId) { return Xrm.WebApi.retrieveRecord(new_campain, planId, ?$select_new_applyid_value) .then(function (plan) { var planApplyUserId plan._new_applyid_value; if (String(planApplyUserId).toLowerCase() ! String(applyUserId).toLowerCase()) { setVal(formContext, new_compainid, null); // 清空选择 alertMsg(只能选择申请人名下的市场活动计划。); return false; } return true; }, function () { setVal(formContext, new_compainid, null); }); }清空会再次触发 onChange加一个 lookupId 为 null 直接 return 的守卫即可避免递归。三、坑二$select 查 lookup 字段要写引用名在写上面这个反查时踩了第二个坑。一开始按 schema 名查// ✗ 写 schema 名 → 旧版客户端可能不返回对应属性取值恒为空 Xrm.WebApi.retrieveRecord(new_campain, planId, ?$selectnew_applyid)返回体里就是没有_new_applyid_value取值恒为空导致校验误报只能选择申请人名下的市场活动计划——明明选的是对的。原理D365 Web API 遵循 OData 协议lookup 字段本质是导航属性它的标量值以引用名形式暴露_字段名_value。所以正确写法是// ✓ 写引用名 → 返回体里取 _new_applyid_value 才是引用记录的 GUID Xrm.WebApi.retrieveRecord(new_campain, planId, ?$select_new_applyid_value) .then(function (plan) { var applyUserId plan._new_applyid_value; });新旧客户端对$select 写 schema 名的容错行为还不一致新版有的会顺手把引用名也带出来所以写引用名是唯一稳的写法。四、三层防线前端只是体验后端才是安全回头看这个需求最终落地是三层同口径校验addPreSearch 过滤体验层让大部分用户看不到不该选的记录onChange 反查体验兜底拦 MRU 和任何过滤漏网的选择立刻清空 提示插件 Create/Update Pre 校验安全层同口径再查一遍不匹配直接抛InvalidPluginExecutionException保存被拒。// 插件兜底Create PreUpdate 侧 PreValidation 同口径 EntityReference applyUserRef target.GetAttributeValueEntityReference(new_applyuser); EntityReference selectedPlanRef target.GetAttributeValueEntityReference(new_compainid); if (applyUserRef ! null selectedPlanRef ! null) { Entity plan svc.Retrieve(new_campain, selectedPlanRef.Id, new ColumnSet(new_applyid)); EntityReference planApplyRef plan.GetAttributeValueEntityReference(new_applyid); if (planApplyRef null || planApplyRef.Id ! applyUserRef.Id) throw new InvalidPluginExecutionException(只能选择申请人名下的市场活动计划); }核心思想界面过滤只能当体验优化不能当规则。前端负责不让用户犯错后端负责用户犯错也进不来——控制台里绕 JS 太容易了。五、总结MRU 是客户端缓存直接渲染不过服务端过滤链旧版客户端无官方禁用 API——业务规则类校验必须后端兜底OData 里 lookup 的值在_字段_value$select写 schema 名拿不到值是经典坑写引用名才稳前端拦截 后端兜底是安全规则的标准双防线。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑