我的异步服务有一种非常奇怪的行为。
故事是:
有一个插件,会在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}类似在自动编号中)
答案 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的事件执行管道如下:
- 事件发生了。
- 事件侦听器捕获事件并根据以下参数将其发送到处理程序sync-async&amp;术前 - 术后 (异步 - 我的情况下的后期操作)
- 然后事件进入Async Queue Agent,决定何时执行插件。
- 与此事件插件相关的异步队列代理运行。
- 插件完成他的工作,然后在成功时返回0(例如)或在失败时返回1。
- 如果为0,Async Queue Agent将关闭当前状态为Succeeded的管道,并向CRM核心发送通知。
醇>
错误可能是在代码(步骤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;
}
}