异步服务多次处理实体

异步服务多次处理实体

问题描述:

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

问题如下: 当我运行批量创建导联(编号碰撞测试)例如400,并且自动编号计数器从0开始,当所有的线索被处理时,我的自动编号计数器以〜770的值结束,远远超过了估计的400.

经验证我发现异步服务多次处理相同的线索。对于一些人来说,只有一次,对于其他人来说是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: 首先我上下文工厂服务变量声明在类,所以它们可以用于一个实例多个线程。

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其余的操作具有Retry Count = 1,和16后是Retry Count = 2等11个操作

(在这个测试中,我创建20根引线programmaticaly,并分配新建分配FY后的计数器显示我last number = 33,如果我总结所有retry count值将其与33,这是类同last number在自动编号)

screenshot

出来
+1

哪里'context'和'service'从何而来?看起来你的代码不是线程安全的。 –

+0

@HenkvanBoeijen,我回答了更新1中的问题。 –

+0

重试可能是尝试获取锁定并跳过的工作流程,您应该尝试重新尝试使用Thread.Sleep,以便工作流程等待锁定。 – dynamicallyCRM

我发现了这个问题。 经过11次尝试所有后续任务CRM已显示插件错误(这是Generic SQL Error没有任何额外的信息,我想这可能是由过载,某种SQL Timeout Error)。

的CRM事件执行管道以下:

  1. 事件发生。
  2. 事件监听cathes事件,并将其发送到基于以下参数同步异步&术前处理程序 - 术后 (异步 - 术后在我的情况)
  3. 然后事件进入异步队列剂决定何时执行该插件。
  4. 异步队列代理运行与此事件插件相关。
  5. 插件做他的工作,然后返回0(例如)成功时,或1失败时返回。
  6. 如果为0,则异步队列代理会关闭当前管道,状态为成功,并将通知发送到CRM核心。

错误代码里面的Autonumbering更新(第5步)的实体,而是在完成之前与地位的任务成功(步骤6)之后大概出现了。

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

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

我推断对整个execute()方法我锁()语句和移动更新的实体请求方法结束。

一切顺利。缺点是这种方式将我的插件变成(几乎)旧的同步,但正如我所说我的服务器不是如此超载,不能承受这个问题。

我后我的代码由于历史原因:

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; 
    } 
} 
+0

此外,这可能有助于控制重试: http://develop1.net/public/post/Control-Async-Workflow-Retries.aspx –

我不能告诉你它为什么会被多次处理,除非你没有从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; 
} 
+0

不幸的是,这并没有解决我的问题。另外,请参阅上面问题中的“更新2”。 –