【问题标题】:Async Service processes entity multiple times异步服务多次处理实体
【发布时间】:2016-09-26 15:08:45
【问题描述】:

我的异步服务有一个非常奇怪的行为。 故事是: 有一个插件,在Lead Create 上触发。插件本身的目的是创建潜在客户的自定义枚举。该插件从保存数字的自动编号实体中的字段中获取最后一个数字。然后插件将自动编号实体的编号字段加 1,并将获得的编号分配给潜在客户。

问题如下: 当我运行大量创建潜在客户(编号碰撞测试)时,例如400,自动编号计数器从 0 开始,当所有潜在客户处理完毕后,我的自动编号计数器以 ~770 的值结束,比估计的 400 多得多。

根据经验,我发现异步服务多次处理相同的线索。有些人只有1次,有些人是2-5次。

为什么会这样?

这是我的代码:

public void Execute(IServiceProvider serviceProvider)
{
    Entity target = ((Entity)context.InputParameters["Target"]);
    target["new_id"] = GetCurrentNumber(service, LEAD_AUTONUMBER);
    service.Update(target);
    return;
}

public int GetCurrentNumber(IOrganizationService service, Guid EntityType)
{
    lock (_locker)
    {
        Entity record = service.Retrieve("new_autonumbering", EntityType, new ColumnSet("new_nextnumber"));
        record["new_nextnumber"] = int.Parse(record["new_nextnumber"].ToString()) + 1;
        service.Update(record);

        return int.Parse(record["new_nextnumber"].ToString());
    }
}

更新 1: 首先,我的 context-factory-service 变量在类中声明,因此它们可以在一个实例中用于多个线程。

public class IdAssignerPlugin : IPlugin
{
    private static      IPluginExecutionContext context;
    private static      IOrganizationServiceFactory factory;
    private static      IOrganizationService service;

    public void Execute(IServiceProvider serviceProvider)
    {
        context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        service = factory.CreateOrganizationService(null);
        [...]
    }
}

@HenkvanBoeijen 的评论之后,我意识到这不是安全的方式,所以我将所有声明移到了 Execute() 方法中。

public class IdAssignerPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));;
        IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));;
        IOrganizationService service = factory.CreateOrganizationService(null);;

        [...]
    }
}

但这并没有让我免于多次处理,尽管现在处理速度非常快。

更新 2: 在 System Jobs 中,我还注意到在 11 次操作后状态为 Retry Count = 0 其余操作为 Retry Count = 1,16 次后为 Retry Count = 2 等。

(在这个测试中,我以编程方式创建了 20 个潜在客户,分配后计数器显示last number = 33,如果我总结所有retry count 值,它会得到33,这类似于自动编号中的last number

【问题讨论】:

  • contextservice 来自哪里?看起来您的代码不是线程安全的。
  • @HenkvanBoeijen ,我在问题的更新 1 中回答了。
  • 重试可能是工作流试图获取锁并跳过,您应该尝试使用 Thread.Sleep 重试,以便工作流等待获取锁。
  • @dynamicallyCRM ,当工作流遇到lock()时,默认休眠。

标签: c# asynchronous plugins dynamics-crm-2011 crm


【解决方案1】:

我无法告诉您为什么要多次处理它,除非您没有从 IServiceProvider 获取上下文和服务,否则您做错了。

防止这种情况发生的一种简单方法是在您的插件首次触发时检查SharedPluginVariable。如果存在则退出,如果不存在则添加共享插件变量。我默认为所有插件执行此操作,以防止插件自身触发的无限循环。

/// <summary>
/// Allows Plugin to trigger itself.  Delete Messge Types always return False 
/// since you can't delete something twice, all other message types return true 
/// if the execution key is found in the shared parameters.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected virtual bool PreventRecursiveCall(IExtendedPluginContext context)
{
    if (context.Event.Message == MessageType.Delete)
    {
        return false;
    }

    var sharedVariables = context.SharedVariables;
    var key = $"{context.PluginTypeName}|{context.Event.MessageName}|{context.Event.Stage}|{context.PrimaryEntityId}";
    if (context.GetFirstSharedVariable<int>(key) > 0)
    {
        return true;
    }

    sharedVariables.Add(key, 1);
    return false;
}

【讨论】:

  • 不幸的是,这并不能解决我的问题。另外,请参阅上述问题中的Update 2
【解决方案2】:

我发现了问题。 在对以下所有任务进行 11 次尝试后,CRM 一直显示插件错误(它是 Generic SQL Error,没有任何其他信息,我想这可能是由过载引起的,某种 SQL Timeout Error)。

crm 的事件执行管道如下:

  1. 事件发生了。
  2. 事件侦听器根据以下参数同步异步和操作前-操作后处理事件并将其发送到处理程序 (异步 - 在我的情况下是后期操作)
  3. 然后事件进入异步队列代理,该代理决定何时执行插件。
  4. 与此事件插件相关的异步队列代理运行。
  5. 插件完成他的工作,然后在成功时返回 0(例如)或在失败时返回 1。
  6. 如果为 0,Async Queue Agent 将关闭当前管道,状态为 Succeeded,并向 CRM 核心发送通知。

错误可能是在代码实体内的Autonumbering 更新之后(步骤 5),但在完成状态为成功的任务之前(步骤 6)。

因此,由于此错误,CRM 再次使用相同的 InputParameters 运行任务。

我的 CRM 服务器不是很过载,所以我想出了以下解决方法:

我将我的 lock() 语句外推到整个 Execute() 方法上,并将 Update Entity 请求移到方法的末尾。

一切顺利。缺点是这种方式将我的插件恢复为(几乎)旧的同步,但正如我所说,我的服务器并没有过载到无法承受这个问题。

出于历史原因,我发布了我的代码:

public class IdAssignerPlugin : IPlugin
{
    public const string AUTONUMBERING_ENTITY = "new_autonumber";
    public static Guid LEAD_AUTONUMBER = 
new Guid("yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy");

    static readonly object _locker = new object();

    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(
typeof(IPluginExecutionContext));
        var factory = (IOrganizationServiceFactory)serviceProvider.GetService(
typeof(IOrganizationServiceFactory));
        var service = factory.CreateOrganizationService(null);

        if (PreventRecursiveCall(context))
        return;

        lock (_locker)
        {
            if (context.InputParameters.Contains("Target") && 
context.InputParameters["Target"] is Entity)
            {
                Entity autoNumber;
                Entity target = ((Entity)context.InputParameters["Target"]);
                if (target.LogicalName.Equals("lead",
 StringComparison.InvariantCultureIgnoreCase))
                {
                    autoNumber = GetCurrentNumber(service, LEAD_AUTONUMBER);
                    target["new_id"] = autoNumber["new_nextnumber"];
                }
                service.Update(autoNumber);
                service.Update(target);
            }
        }
        return;
    }

    public int GetCurrentNumber(IOrganizationService service, Guid EntityType)
    {
        Entity record = 
service.Retrieve(AUTONUMBERING_ENTITY, EntityType, new ColumnSet("new_nextnumber"));
        record["new_nextnumber"] = int.Parse(record["new_nextnumber"].ToString()) + 1;
        return record;
    }

    protected virtual bool PreventRecursiveCall(IPluginExecutionContext context)
    {
        if (context.SharedVariables.Contains("Fired")) return true;
        context.SharedVariables.Add("Fired", 1);
        return false;
    }
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 2018-06-23
相关资源
最近更新 更多