内部部署 Dynamics CRM 2011。 (但这个问题存在于许多远离 Dynamics CRM 的情况下。)
CRM 插件有一个入口点:
void IPlugin.Execute (IServiceProvider 服务提供者)
( http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.iplugin.execute.aspx )
serviceProvider 是对插件执行上下文的引用。插件所做的任何有用的事情都需要访问 serviceProvider 或其成员。
有些插件又大又复杂,包含多个类。例如,我正在开发一个插件,它有一个实例化多次的类。该类需要使用serviceProvider。
从所有需要它的类访问 serviceProvider 的一种方法是向所有这些类添加一个属性,然后设置该属性。或者为每个类需要的 serviceProvider 部分添加属性。这两种方法中的任何一种都会导致大量重复代码。
另一种方法是在线程范围内拥有一个全局变量。然而,根据http://msdn.microsoft.com/en-us/library/cc151102.aspx一个“不应在插件中使用全局类变量。”
那么访问 serviceProvider 而无需到处传递的最佳方式是什么?
附言如果示例有帮助,serviceProvider 提供对日志记录对象的访问。我希望几乎每个类(class)都记录下来。我不想将对日志记录对象的引用传递给每个类。
最佳答案
这不是文档中的警告所要表达的意思。 IServiceProvider
在此上下文中不是全局变量;它是一个方法参数,因此每次调用 Execute
都会获得自己的提供程序。
For improved performance, Microsoft Dynamics CRM caches plug-in instances. 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. In addition, multiple threads could be running the plug-in at the same time. All per invocation state information is stored in the context. This means that you should not use global class variables in plug-ins [Emphasis mine].
将对象从上下文传递给需要它们的辅助类没有任何问题。该警告建议不要将某些内容存储在插件类本身的字段(“类变量”)中,这可能会影响对同一实例的 Execute
的后续调用,或导致如果 Execute
被同一实例上的多个线程同时调用,则并发问题。
当然,这个“全局性”不得不被认为是传递性的。如果您将任何内容存储在插件类 或 中的辅助类中,以多次调用 Execute
可以访问的任何方式(使用例如,插件类或插件类或帮助类上的静态信息),您就会面临同样的问题。
作为一个单独的考虑,我会编写所涉及的帮助程序类来接受尽可能特定于它们的功能的类型——下降到单个实体的级别——而不是整个 IServiceProvider
。测试一个只需要 EntityReference
的类比测试一个需要整个 IServiceProvider
和 IPluginExecutionContext
模拟的类要容易得多。
关于全局变量与注入(inject)类所需的值
没错,这在面向对象的代码中随处可见。看看这两个实现:
public class CustomEntityFrubber
{
public CustomEntityFrubber(IOrganizationService service, Guid entityIdToFrub)
{
this.service = service;
this.entityId = entityIdToFrub;
}
public void FrubTheEntity()
{
// Do something with service and entityId.
}
private readonly IOrganizationService service;
private readonly Guid entityId;
}
// Initialised by the plugin's Execute method.
public static class GlobalPluginParameters
{
public static IOrganizationService Service
{
get { return service; }
set { service = value; }
}
public static Guid EntityIdToFrub
{
get { return entityId; }
set { entityId = value; }
}
[ThreadStatic]
private static IOrganizationService service;
[ThreadStatic]
private static Guid entityId;
}
public class CustomEntityFrubber
{
public FrubTheEntity()
{
// Do something with the members on GlobalPluginParameters.
}
}
所以假设您已经实现了类似于第二种方法的东西,现在您有一堆使用 GlobalPluginParameters
的类。一切都很顺利,直到您发现它们中的 一个 偶尔会失败,因为它需要通过调用 CreateOrganizationService(null)
获得的 IOrganizationService
实例,因此它以系统用户而不是调用用户(并不总是拥有所需权限)的身份访问 CRM。
修复第二种方法需要您将另一个字段添加到不断增长的全局变量列表中,记住将其设为 ThreadStatic
以避免并发问题,然后更改 CustomEntityFrubber
使用新的 SystemService
属性。所有这些类之间都有紧密的耦合。
不仅如此,所有这些全局变量都在插件调用之间徘徊。如果您的代码有一个错误,以某种方式绕过了 GlobalPluginParameters.EntityIdToFrub
的分配,您的插件突然莫名其妙地操作当前调用 Execute
时未传递给它的数据>.
CustomEntityFrubber
需要哪些全局变量也不是很明显,除非您阅读它的代码。将它乘以您拥有的帮助程序类的数量,维护此代码开始变得令人头疼。 “现在,这个对象需要我在调用它之前设置 Guid1
或 Guid2
吗?”最重要的是,该类本身无法确定其他一些代码不会去更改它所依赖的全局变量的值。
如果您使用第一种方法,您只需将不同的值传递给 CustomEntityFrubber
构造函数,无需进一步更改代码。此外,周围没有陈旧的数据。构造函数使类具有哪些依赖项一目了然,并且一旦具有这些依赖项,就可以确保它们不会更改,除非以它们设计的方式进行更改。
关于c# - 如何避免在类之间传递上下文引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19119714/