You need to enable JavaScript to run this app. Dynamics 365 实现DNI重复校验阻止表单保存时死循环问题求解
优惠活动
大模型
产品
解决方案
定价
更多

Dynamics 365 实现DNI重复校验阻止表单保存时死循环问题求解

问题根因

死循环的本质逻辑很简单:只要触发保存动作,不管是用户点界面上的保存按钮,还是代码手动调用保存接口,都会触发绑定在OnSave事件上的所有函数。你现在的流程是:

  1. 用户点保存 → 触发saveDni → 拦截默认保存流程
  2. 异步查询DNI是否重复,校验通过后手动调用formContext.data.entity.save()
  3. 这个手动触发的保存又会运行一次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 00:15:03