异步服务多次处理实体
Async Service processes entity multiple times
我的异步服务有一个非常奇怪的行为。
故事是:
有一个插件,在 Lead Create
上触发。插件本身的目的是创建线索的自定义枚举。该插件从保留数字的自动编号实体中的字段中获取最后一个数字。然后插件将自动编号实体的编号字段递增 1,并将获得的编号分配给 Lead。
问题如下:
当我 运行 大量创建潜在客户时(编号的碰撞测试)例如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:
首先,我的上下文工厂服务变量在 class 中声明,因此它们可以在一个实例中用于多个线程。
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 次之后它是 Retry Count = 2
,等等
(在此测试中,我以编程方式创建了 20 个潜在客户,在分配后,计数器显示 last number = 33
,如果我总结所有 retry count
值,则结果为 33,这类似于 last number
在自动编号中)
我不能告诉你为什么它被多次处理,除非你没有从你的 IServiceProvider 获得你的上下文和你的服务,你做错了。
防止这种情况发生的一个简单方法是在您的插件首次启动时检查 SharedPluginVariable。如果存在则退出,如果不存在则添加shared插件变量。我默认为所有插件执行此操作,以防止使用触发自身的插件无限循环。
/// <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;
}
我发现了问题。
在对以下所有任务进行 11 次尝试后,CRM 一直显示插件错误(Generic SQL Error
,没有任何附加信息,我想这可能是由过载引起的,某种 SQL Timeout Error
)。
crm 的事件执行管道如下:
- Event happened.
- Event listener cathes event and sends it to the handler based on following parameters sync-async & pre-operation - post-operation
(async - post-operation in my case)
- Then event goes into Async Queue Agent which decides when to execute the plugin.
- Async Queue Agent runs related to this event plugin.
- Plugin does his work and then returns 0 (for e.g.) when succeeded or 1 when failed.
- If 0, Async Queue Agent closes current pipeline with the status of Succeeded and sends notification to CRM core.
错误可能是在更新代码(第 5 步)实体内的 Autonumbering
之后出现的,但在完成状态为成功的任务(第 6 步)之前。
因此,由于此错误,CRM 运行 任务再次具有相同的 InputParameters。
我的 CRM 服务器不是很重载,所以我提出了以下解决方法:
我在整个 Execute() 方法上推断我的 lock() 语句,并将更新实体请求移动到方法的末尾。
一切顺利。缺点是这种方式将我的插件变回(几乎)旧的同步,但正如我所说,我的服务器并没有过载到无法承受这个问题。
我 post 由于历史原因我的代码:
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;
}
}
我的异步服务有一个非常奇怪的行为。
故事是:
有一个插件,在 Lead Create
上触发。插件本身的目的是创建线索的自定义枚举。该插件从保留数字的自动编号实体中的字段中获取最后一个数字。然后插件将自动编号实体的编号字段递增 1,并将获得的编号分配给 Lead。
问题如下: 当我 运行 大量创建潜在客户时(编号的碰撞测试)例如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: 首先,我的上下文工厂服务变量在 class 中声明,因此它们可以在一个实例中用于多个线程。
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 次之后它是 Retry Count = 2
,等等
(在此测试中,我以编程方式创建了 20 个潜在客户,在分配后,计数器显示 last number = 33
,如果我总结所有 retry count
值,则结果为 33,这类似于 last number
在自动编号中)
我不能告诉你为什么它被多次处理,除非你没有从你的 IServiceProvider 获得你的上下文和你的服务,你做错了。
防止这种情况发生的一个简单方法是在您的插件首次启动时检查 SharedPluginVariable。如果存在则退出,如果不存在则添加shared插件变量。我默认为所有插件执行此操作,以防止使用触发自身的插件无限循环。
/// <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;
}
我发现了问题。
在对以下所有任务进行 11 次尝试后,CRM 一直显示插件错误(Generic SQL Error
,没有任何附加信息,我想这可能是由过载引起的,某种 SQL Timeout Error
)。
crm 的事件执行管道如下:
- Event happened.
- Event listener cathes event and sends it to the handler based on following parameters sync-async & pre-operation - post-operation (async - post-operation in my case)
- Then event goes into Async Queue Agent which decides when to execute the plugin.
- Async Queue Agent runs related to this event plugin.
- Plugin does his work and then returns 0 (for e.g.) when succeeded or 1 when failed.
- If 0, Async Queue Agent closes current pipeline with the status of Succeeded and sends notification to CRM core.
错误可能是在更新代码(第 5 步)实体内的 Autonumbering
之后出现的,但在完成状态为成功的任务(第 6 步)之前。
因此,由于此错误,CRM 运行 任务再次具有相同的 InputParameters。
我的 CRM 服务器不是很重载,所以我提出了以下解决方法:
我在整个 Execute() 方法上推断我的 lock() 语句,并将更新实体请求移动到方法的末尾。
一切顺利。缺点是这种方式将我的插件变回(几乎)旧的同步,但正如我所说,我的服务器并没有过载到无法承受这个问题。
我 post 由于历史原因我的代码:
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;
}
}