Dynamics CRM Online 对象缓存未正确缓存
Dynamics CRM Online Object caching not caching correctly
我有一个要求,我们需要一个插件来从外部系统检索会话 ID 并将其缓存一段时间。我在实体上使用一个字段来测试会话是否真的被缓存了。当我多次刷新 CRM 表单时,从输出来看,同一个密钥似乎有四个版本(在任何时候都是一致的)。我已经尝试清除缓存并再次测试,但仍然是相同的结果。
感谢任何帮助,提前致谢。
每次刷新页面的输出:
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
为此,我实现了以下代码:
public class SessionPlugin : IPlugin
{
public static readonly ObjectCache Cache = MemoryCache.Default;
private static readonly string _sessionField = "new_sessionid";
#endregion
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
return;
var userId = context.InitiatingUserId.ToString();
// Use the userid as key for the cache
var sessionId = CacheSessionId(userId, GetSessionId(userId));
sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";
// Assign session id to entity
var entity = (Entity)context.OutputParameters["BusinessEntity"];
if (entity.Contains(_sessionField))
entity[_sessionField] = sessionId;
else
entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
private string CacheSessionId(string key, string sessionId)
{
// If value is in cache, return it
if (Cache.Contains(key))
return Cache.Get(key).ToString();
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
Priority = CacheItemPriority.Default
};
Cache.Add(key, sessionId, cacheItemPolicy);
return sessionId;
}
private string GetSessionId(string user)
{
// this will be replaced with the actual call to the external service for the session id
return DateTime.Now.ToString("yyyyMMdd_hhmmss");
}
}
Daryl 在这里对此进行了很好的解释:
基本上您没有为整个 CRM 系统提供一个 MemoryCache 实例,您的代码只是证明每个插件都有多个应用程序域,因此即使存储在此类插件中的静态变量也可以有多个值,您不能依赖这一点. MSDN 上没有文档可以解释 sanboxing 是如何工作的(尤其是在这种情况下的应用程序域),但是使用静态变量当然不是一个好的 idea.Of 如果你是在线处理,你不能确定是否有只有一个前端服务器或多个前端服务器(这也会导致这种行为)
Class 级变量应限于配置信息。不支持像您一样使用 class 级别变量。在 CRM Online 中,由于有多个 Web 前端,特定请求可能会由插件的不同实例 class 在不同的服务器上执行,而不是另一个请求。总的来说,假设 CRM 是无状态的,除非持久化和检索,否则插件执行之间不应假设任何内容是连续的。
根据 SDK:
The plug-in's Execute method should be written to be stateless because
the constructor is not called for every invocation of the plug-in.
Also, multiple system threads could execute the plug-in at the same
time. All per invocation state information is stored in the context,
so you should not use global variables or attempt to store any data in
member variables for use during the next plug-in invocation unless
that data was obtained from the configuration parameter provided to
the constructor.
我有一个要求,我们需要一个插件来从外部系统检索会话 ID 并将其缓存一段时间。我在实体上使用一个字段来测试会话是否真的被缓存了。当我多次刷新 CRM 表单时,从输出来看,同一个密钥似乎有四个版本(在任何时候都是一致的)。我已经尝试清除缓存并再次测试,但仍然是相同的结果。
感谢任何帮助,提前致谢。
每次刷新页面的输出:
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
为此,我实现了以下代码:
public class SessionPlugin : IPlugin
{
public static readonly ObjectCache Cache = MemoryCache.Default;
private static readonly string _sessionField = "new_sessionid";
#endregion
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
return;
var userId = context.InitiatingUserId.ToString();
// Use the userid as key for the cache
var sessionId = CacheSessionId(userId, GetSessionId(userId));
sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";
// Assign session id to entity
var entity = (Entity)context.OutputParameters["BusinessEntity"];
if (entity.Contains(_sessionField))
entity[_sessionField] = sessionId;
else
entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
private string CacheSessionId(string key, string sessionId)
{
// If value is in cache, return it
if (Cache.Contains(key))
return Cache.Get(key).ToString();
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
Priority = CacheItemPriority.Default
};
Cache.Add(key, sessionId, cacheItemPolicy);
return sessionId;
}
private string GetSessionId(string user)
{
// this will be replaced with the actual call to the external service for the session id
return DateTime.Now.ToString("yyyyMMdd_hhmmss");
}
}
Daryl 在这里对此进行了很好的解释:
基本上您没有为整个 CRM 系统提供一个 MemoryCache 实例,您的代码只是证明每个插件都有多个应用程序域,因此即使存储在此类插件中的静态变量也可以有多个值,您不能依赖这一点. MSDN 上没有文档可以解释 sanboxing 是如何工作的(尤其是在这种情况下的应用程序域),但是使用静态变量当然不是一个好的 idea.Of 如果你是在线处理,你不能确定是否有只有一个前端服务器或多个前端服务器(这也会导致这种行为)
Class 级变量应限于配置信息。不支持像您一样使用 class 级别变量。在 CRM Online 中,由于有多个 Web 前端,特定请求可能会由插件的不同实例 class 在不同的服务器上执行,而不是另一个请求。总的来说,假设 CRM 是无状态的,除非持久化和检索,否则插件执行之间不应假设任何内容是连续的。
根据 SDK:
The plug-in's Execute method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables or attempt to store any data in member variables for use during the next plug-in invocation unless that data was obtained from the configuration parameter provided to the constructor.