异步服务多次处理实体
我有一个非常奇怪的行为,我的异步服务。 故事是: 有一个插件,它在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
在自动编号)
我发现了这个问题。 经过11次尝试所有后续任务CRM已显示插件错误(这是Generic SQL Error
没有任何额外的信息,我想这可能是由过载,某种SQL Timeout Error
)。
的CRM事件执行管道以下:
- 事件发生。
- 事件监听cathes事件,并将其发送到基于以下参数同步异步&术前处理程序 - 术后 (异步 - 术后在我的情况)
- 然后事件进入异步队列剂决定何时执行该插件。
- 异步队列代理运行与此事件插件相关。
- 插件做他的工作,然后返回0(例如)成功时,或1失败时返回。
- 如果为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;
}
}
此外,这可能有助于控制重试: 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;
}
不幸的是,这并没有解决我的问题。另外,请参阅上面问题中的“更新2”。 –
哪里'context'和'service'从何而来?看起来你的代码不是线程安全的。 –
@HenkvanBoeijen,我回答了更新1中的问题。 –
重试可能是尝试获取锁定并跳过的工作流程,您应该尝试重新尝试使用Thread.Sleep,以便工作流程等待锁定。 – dynamicallyCRM