Dynamics 365 实现DNI重复校验阻止表单保存时死循环问题求解
问题根因
死循环的本质逻辑很简单:只要触发保存动作,不管是用户点界面上的保存按钮,还是代码手动调用保存接口,都会触发绑定在OnSave事件上的所有函数。你现在的流程是:
- 用户点保存 → 触发
saveDni→ 拦截默认保存流程 - 异步查询DNI是否重复,校验通过后手动调用
formContext.data.entity.save() - 这个手动触发的保存又会运行一次
saveDni,再次执行拦截、查询、手动保存的流程,形成无限循环。
解决方法
方案一:加流程标记位区分触发来源
这是最稳定、不需要改动原有事件绑定的方案,核心逻辑是给脚本加一个模块级的状态标记,用来判断当前进入OnSave的流程,是需要校验的用户主动操作,还是校验完成后准备放行的内部触发:
- 第一次执行
saveDni时,先检查标记状态:如果标记是「已放行」,就直接跳过拦截、查询所有逻辑,让默认保存流程正常走完 - 如果标记是初始状态(也就是用户主动点了保存),就正常拦截默认保存,做DNI唯一性校验
- 校验通过准备手动触发save之前,先把标记改成「已放行」,再调用save方法。
修正后的完整代码如下,同时修复了原代码的隐藏bug:
// 模块级状态标记,用来区分手动触发的保存流程 let allowNextSave = false; function saveDni(executionContext) { try { const formContext = executionContext.getFormContext(); // 如果是校验后触发的放行保存,直接跳过校验逻辑,重置标记后正常走保存流程 if (allowNextSave) { allowNextSave = false; return; } const currentDni = formContext.getAttribute("cr6ff_dni").getValue(); // DNI为空时不需要做重复校验,直接走默认保存逻辑 if (!currentDni) return; // 拦截默认的保存流程,等待校验完成 executionContext.getEventArgs().preventDefault(); // 获取当前编辑的记录ID,编辑场景下用来排除自身的匹配 const currentRecordId = formContext.data.entity.getId()?.replace(/[{}]/g, ""); const fetchXml = [ "<fetch top='1'>", " <entity name='cr6ff_person'>", " <filter>", " <condition attribute='cr6ff_dni' operator='eq' value='" + currentDni + "'/>", // 排除当前正在编辑的记录,避免编辑时把自己判成重复 currentRecordId ? "<condition attribute='cr6ff_personid' operator='ne' value='" + currentRecordId + "'/>" : "", " </filter>", " </entity>", "</fetch>" ].join(""); const query = "?fetchXml=" + encodeURIComponent(fetchXml); Xrm.WebApi.retrieveMultipleRecords("cr6ff_person", query).then( (result) => { if (result.entities.length === 0) { // 校验通过:先设置放行标记,再触发保存 allowNextSave = true; formContext.data.entity.save(); } else { // 重复时给用户明确提示,避免无响应的困惑 formContext.ui.setFormNotification("该DNI已被其他人员记录注册", "ERROR", "dni_duplicate"); // 延迟清除提示,避免残留干扰后续操作 setTimeout(() => { formContext.ui.clearFormNotification("dni_duplicate"); }, 3000); } } ).catch((error) => { console.error("DNI校验请求失败", error); }); } catch (error) { console.log("An error has occurred while validating the user's data."); } }
使用这个方案的注意事项:
- 标记位是存在当前表单的脚本运行上下文中的,表单不刷新就不会丢失,不会出现跨表单串值的问题
- 一定要在放行流程里重置标记,不然用户下次主动点保存时,因为标记没有复位,会直接跳过校验逻辑,失去DNI唯一性判断的作用。
方案二:临时解绑事件钩子
如果不想用状态标记,也可以在触发手动保存前,临时把saveDni函数从OnSave事件上解绑,等保存流程走完后再重新绑回去:
// 校验通过后,准备手动保存 const handleSaveComplete = () => { // 保存完成后,重新绑定OnSave事件,恢复校验逻辑 formContext.data.entity.addOnSave(saveDni); }; try { // 临时移除OnSave上的saveDni绑定,避免手动save触发二次执行 formContext.data.entity.removeOnSave(saveDni); const savePromise = formContext.data.entity.save(); // 保存流程结束后重新绑定事件 savePromise.finally(handleSaveComplete); } catch (e) { // 出错时也要重新绑定事件,避免事件永久解绑 handleSaveComplete(); console.error("保存流程异常", e); }
使用这个方案的注意事项:一定要在异常分支里也做重新绑定的操作,不然一旦请求报错,事件会一直处于解绑状态,后续的保存操作都不会触发DNI校验。
原代码的隐藏问题需要一起修复
- 原代码拼接的fetchXml存在语法错误:
<entity name= 'cr6ff_person'">引号位置错误,会直接导致WebApi请求失败 - 查询逻辑没有排除当前编辑的记录:修改已有人员记录时,查询DNI会匹配到记录自身,哪怕只是修改了姓名,也会被判定成DNI重复,阻止正常保存
- WebApi请求没有加异常捕获:如果请求超时或者报错,因为已经拦截了默认保存流程,会出现用户点了保存完全没反应的问题
- 查到重复DNI时没有给用户明确的提示,用户会因为保存无响应不知道哪里出了问题。
内容的提问来源于stack exchange,提问作者Etto
相关产品推荐
相关产品推荐

