异步服务多次处理实体

时间:2016-09-26 15:08:45

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

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

问题如下: 当我进行大量创建潜在客户(编号的崩溃测试)时,例如400,自动编号计数器从0开始,当处理所有潜在客户时,我的自动编号计数器以~770的值结束,远远超过估计的400。

我根据经验发现,Async服务处理多次同样导致。有的只有一次,对于其他人则是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:在系统工作中我还注意到,在状态为Retry Count = 0的11次操作之后,其余操作都为Retry Count = 1,而在16之后,它为{{1}等等。

(在此测试中,我创建了20个主题程序,并且在分配后,计数器显示Retry Count = 2,如果我总结所有last number = 33值,则会显示33,这与{{1}类似在自动编号中)

screenshot

2 个答案:

答案 0 :(得分:0)

我无法告诉你为什么它被多次处理,除非你没有从你的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;
}

答案 1 :(得分:0)

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

crm的事件执行管道如下:

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

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

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

我的CRM服务器没有超载,所以我提出了以下解决方法:

我在整个Execute()方法上推断我的lock()语句,并将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;
    }
}