PAYATHON 2026

Dynamic 365 CRM 如何合并项目并减少 step 注册?

技术老齐

结论

可以把 3 个项目合并为一个插件程序集,共用一个 IPlugin 类,按实体 logical name 选择字段映射,无需依赖 Unsecure Configuration。

如果还想把注册数量减到两个 step,可以考虑将 Primary Entity 留空,分别注册 Create / PostOperation / Synchronous 和 Update / PreOperation / Synchronous,再在代码中筛选这三个实体。这样做会让其他实体的相应操作也调用插件,再由代码判断并退出。

如果要求平台只对这三个实体触发插件,建议保留六个实体专属 step,让它们指向同一个插件类,共用一份业务代码。

这些做法适用于 Dynamics 365 CRM 的常规 Dataverse 插件机制。旧版本或本地部署应先在目标环境验证注册行为。

为什么更换配置方式不能直接减少 step

程序集用于部署代码,插件类提供执行入口,step 定义触发条件。多个 step 可以引用同一个插件类。

Unsecure Configuration 是传给插件构造函数的配置字符串,可以保存字段映射,但不会改变 step 的实体筛选范围。换成 Secure Configuration 或自定义配置表,也不会自动合并注册。Microsoft Learn:编写插件

普通 step 的 Primary Entity 只能指定一个实体,不能把三个 logical name 填成实体列表。对于允许不指定实体的消息,留空会让插件响应所有支持该消息的实体;这里没有“只选这三个实体”的多选条件。Microsoft Learn:注册插件

如何合并项目

建议先合并代码,再决定是否使用全局 step:

  1. 将相同的业务处理提取到公共方法或服务类中。
  2. 建立实体 logical name 到字段 logical name 的映射。
  3. 在插件入口读取 context.PrimaryEntityName,找到映射后调用公共逻辑。
  4. 将现有 step 改为引用合并后的插件类,保留原来的阶段、执行模式和执行顺序。
  5. 验证替代 step 后,停用旧 step,避免同一操作被重复处理。

三个实体的映射固定时,直接写在代码中即可。如果需要让管理员调整映射,可以放入自定义配置表,同时处理读取权限和缓存失效。这些选择只影响配置的维护方式,step 的触发规则仍然不变。

如果只注册两个 step

先确认目标环境允许相应消息不指定 Primary Entity,再使用以下配置:

配置项Create stepUpdate step
MessageCreateUpdate
Primary Entity留空留空
StagePostOperationPreOperation
Execution ModeSynchronousSynchronous
Event Handler同一个插件类同一个插件类

这里将问题中的“pre sync”理解为 PreOperation + Synchronous。如果现有注册使用的是 PreValidation,应按原有业务要求保留。

全局入口必须先检查消息、阶段和实体,符合条件后再读取配置、查询数据或执行写入。代码中的实体筛选可以减少后续业务处理,但插件被调用的开销仍然存在。

代码示例:按实体映射字段

所有 new_* 名称都是占位符,需要替换为实际 logical name。两个处理方法中的注释位置需要接入原有业务逻辑。

using System;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;

public sealed class SharedEntityPlugin : IPlugin
{
    // 仅保存固定映射,不保存单次调用的上下文或服务对象。
    private static readonly Dictionary<string, string> FieldMap =
        new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            { "new_personincontact", "new_personfield" },
            { "new_visitdelegate", "new_delegatefield" },
            { "new_visitstakeholder", "new_stakeholderfield" }
        };

    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(
            typeof(IPluginExecutionContext));

        bool isCreatePost =
            context.MessageName == "Create" &&
            context.Stage == 40;

        bool isUpdatePre =
            context.MessageName == "Update" &&
            context.Stage == 20;

        // Mode: 0 = Synchronous
        if (context.Mode != 0 || (!isCreatePost && !isUpdatePre))
            return;

        string fieldName;
        if (!FieldMap.TryGetValue(context.PrimaryEntityName, out fieldName))
            return;

        if (!context.InputParameters.Contains("Target"))
            return;

        var target = context.InputParameters["Target"] as Entity;
        if (target == null)
            return;

        // 假设 Update 逻辑只依赖映射中的这个字段。
        if (isUpdatePre && !target.Contains(fieldName))
            return;

        if (isUpdatePre)
        {
            HandleUpdate(target, fieldName, context, serviceProvider);
        }
        else
        {
            HandleCreate(target, fieldName, context, serviceProvider);
        }
    }

    private static void HandleUpdate(
        Entity target,
        string fieldName,
        IPluginExecutionContext context,
        IServiceProvider serviceProvider)
    {
        // 接入原有 Update 公共逻辑。
        // 如需修改当前记录,在 PreOperation 中直接修改 target。
    }

    private static void HandleCreate(
        Entity target,
        string fieldName,
        IPluginExecutionContext context,
        IServiceProvider serviceProvider)
    {
        // 接入原有 Create 公共逻辑。
        // 此时记录已经创建,按需使用 context.PrimaryEntityId。
    }
}

IPluginExecutionContext 提供消息、阶段和参数等执行信息,Target 从 InputParameters 中取得。平台可能复用插件实例,因此不要把当前调用的上下文、实体或服务对象存入实例字段。Microsoft Learn:执行上下文、编写插件

六个实体专属 step 也可以使用这段入口代码,无需为两种注册方式分别实现。

注意事项与建议

Update 中的 Target 不是完整记录

不要假设 Target 包含所有字段。示例中的 Contains(fieldName) 只适用于“该字段出现在请求中才需要处理”的规则。如果业务依赖多个字段,应检查所有相关字段。

未提交字段和显式提交 null 是两种情况,需要分别处理。比较新旧值时,应配置包含所需列的 Pre Image,并确认所选注册方式能够满足图像配置要求。

减少 step 数量不等于减少执行次数

保留六个实体专属 step 时,可以分别为三个 Update step 设置对应的 Filtering Attributes,由平台过滤无关更新。全局 step 则要等插件入口执行后,才能通过代码判断是否继续处理。

Filtering Attributes 判断的是更新请求是否包含指定字段。即使提交的值与原值相同,也可能触发插件。Microsoft Learn:设置筛选属性

保留 Create 和 Update 的阶段差异

在 Update / PreOperation 中,修改当前记录的字段通常直接修改 Target 即可,无需再调用 service.Update(target)。

Create / PostOperation 在主操作之后执行,仅修改内存中的 Target 不会再次保存记录。如果再发起更新,会产生新的 Update 事件,需要检查公共逻辑是否会重复触发。Microsoft Learn:事件执行管道

对于这三个实体,通常建议使用一个程序集、一个插件类和六个实体专属 step。这样只需维护一份公共逻辑,也能保留平台的实体筛选和字段筛选。确实需要全局入口时,应先验证额外调用的开销可以接受,再采用两个全局 step。

备注:内容仅供参考。