如何避免在类之间传递上下文引用

本文关键字:上下文 引用 之间 何避免 | 更新日期: 2023-09-27 18:33:32

>Dynamics CRM 2011 on premise.(但是这个问题在Dynamics CRM之外的许多情况下都存在。

CRM插件有一个入口点:

void IPlugin.Execute (IServiceProvider serviceProvider(

(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"不应该在插件中使用全局类变量"。

那么,在不到处传递服务提供者的情况下访问服务提供商的最佳方法是什么?

附言如果示例有帮助,服务提供程序将提供对日志记录对象的访问。我希望几乎每个班级都记录。我不想将对日志记录对象的引用传递给每个类。

如何避免在类之间传递上下文引用

这不是文档中的警告所要表达的。在此上下文中,IServiceProvider不是全局变量;它是一个方法参数,因此每次调用Execute都有自己的提供程序。

为了提高性能,Microsoft动态 CRM 缓存插件实例。插件的 Execute 方法应编写为无状态,因为每次调用插件时都不会调用构造函数。此外,多个线程可以同时运行插件。所有每个调用状态信息都存储在上下文中。这意味着您不应该在插件中使用全局类变量[强调我的]。

对象从上下文传递到需要它们的帮助程序类并没有错。该警告建议不要在插件类本身的字段("类变量"(中存储某些内容,这可能会影响对同一实例上Execute的后续调用,或者如果同一实例上的多个线程同时调用Execute则会导致并发问题。

当然,这种"全球性"必须被传递地考虑。如果您以多次调用 Execute 可以访问的任何方式将任何内容存储在插件类帮助程序类中(例如,使用插件类上的字段或插件或帮助程序类上的静态(,那么您将面临同样的问题。

作为单独的考虑因素,我会编写所涉及的帮助程序类以接受尽可能特定于其函数的类型 - 直到单个实体的级别 - 而不是整个IServiceProvider。测试只需要一个EntityReference的类比测试一个需要整个IServiceProviderIPluginExecutionContext模拟的类要容易得多。


关于全局变量与注入类所需的值

你是对的,这是在面向对象的代码中随处可见的东西。看看这两个实现:

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需要哪些全局变量也不清楚,除非您阅读其代码。将其乘以您拥有的帮助程序类数量,维护此代码开始变得令人头疼。"现在,这个物体需要我设置Guid1Guid2才能调用它吗?"最重要的是,类本身不能确定其他代码不会去改变它所依赖的全局变量的值。

如果使用第一种方法,则只需将不同的值传递给CustomEntityFrubber构造函数,而无需进一步更改代码。此外,没有陈旧的数据。构造函数清楚地表明类具有哪些依赖项,一旦拥有它们,就可以确保它们不会更改,除非以设计的方式。

正如您所说,您不应该在插件上放置成员变量,因为实例会由插件管道在请求之间缓存和重用。

我采取的方法是创建一个执行所需任务的类,并在构造函数上传递由开发人员工具包 (http://msdn.microsoft.com/en-us/library/hh372957.aspx( 提供的修改后的 LocalPluginContext(使其成为公共类(。然后,您的类可以存储实例以执行其工作,就像处理任何其他代码段一样。您基本上与插件框架施加的限制脱钩。这种方法也使单元测试更容易,因为你只需要为类提供执行上下文,而不是模拟整个插件管道。

值得注意的是,在开发人员工具包中自动生成的 Plugin.cs 类中存在一个错误,它没有设置 ServiceProvider 属性 - 在 LocalPluginContext 的构造函数末尾添加以下行:

这。服务提供商 = 服务提供商;

我已经在插件中看到了一些 IoC 方法的实现 - 但恕我直言,它使插件代码过于复杂。我建议您的插件精简而简单,以避免线程/性能问题。

在这个设计请求中,我会担心很多事情(并不是说它不好,只是应该意识到并预测(。

  1. IOrganizationService 不是多线程安全的。 我假设IServiceProvider的其他方面则不然。
  2. 在 IServiceProvider 级别测试内容要复杂得多,因为必须模拟其他属性
  3. 如果您决定在插件之外调用当前在插件中的逻辑(例如命令行服务(,则需要一种处理日志记录的方法。

如果你不想到处传递对象,简单的解决方案是在某个类上创建一个静态属性,你可以在插件执行时设置它,然后从任何地方访问。

当然,现在您必须处理上面的问题 #1,因此它必须是某种单例管理器,它可能会使用当前线程的 id 来设置和检索该线程的值。 这样,如果插件被触发两次,您可以根据当前正在执行的线程检索正确的上下文。 (编辑而不是一些时髦的线程 id 查找字典,@shambulator 的 ThreadStatic 属性应该有效(

对于问题 #2,我不会按原样存储IServiceProvider,而是拆分它的不同属性(例如 IPluginExecutionContextIOrganizationService等(

对于问题 #3,将操作或函数存储在管理器中而不是对象值本身可能是有意义的。 例如,如果不是存储 IPluginExecutionContext,而是存储一个接受字符串进行记录并使用 IPlurginExeuctionContext 进行记录的 func。 这允许其他代码设置自己的日志记录,而不依赖于从插件中执行。

我自己没有制作过任何这些插件,但我会像 I/O 设备一样对待IServiceProvider

从中获取所需的数据,并将该数据转换为适合您的插件的格式。使用转换后的数据设置其他类。获取其他类的输出,然后翻译回IServiceProvider可以理解和使用的术语。

您的输入和输出取决于IServiceProvider,但处理不一定如此。

来自 爱德华多·阿瓦利亚 at http://social.microsoft.com/Forums/en-US/f433fafa-aff7-493d-8ff7-5868c09a9a9b/how-to-avoid-passing-a-context-reference-among-classes

好吧,正如SO的某人已经告诉您的那样,全局变量限制的存在会导致插件在相同的上下文(对象上下文和可能的其他环境条件(中调用时不会再次实例化,因此任何自定义全局变量都将在该实例之间共享,但是由于上下文将是相同的,如果您想在很多类之间共享它,将其分配给全局变量没有问题。

无论如何,我宁愿在构造函数上传递上下文并共享它,以便对它有更多的控制权,但这只是我。

相关文章: