简单的注射器:在相同的图形服务注入相同的UnitOfWork实例 [英] Simple Injector: Inject same UnitOfWork instance across services of the same graph

查看:300
本文介绍了简单的注射器:在相同的图形服务注入相同的UnitOfWork实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有多个服务,每个都有一个的UnitOfWork 使用的简单的注射器 IoC容器。

目前我能看到每个的UnitOfWork 实例是一个单独的对象,这是不好的,因为我使用实体框架,并要求各工作的所有单位同一背景下的参考。

我如何才能确保相同的的UnitOfWork 实例被注入到每个每个解析请求的所有服务?我的 UnitOfWor 命令结束后,将保存的外部命令处理程序装饰。

请注意,这是一个公共库,将同时用于MVC和Windows窗体,这将是很好有两个平台,如果可能的一个通用的解决方案。

code是如下:

  //将code片段,注册类型
无效RegisterTypes()
{
    //通过广大服务层注册工作类的一般单位使用
    container.Register&所述; IUnitOfWork,的UnitOfWork>();

    //提供了一个工厂单类来创建自己的工作单位
    // 随意
    container.RegisterSingle&所述; IUnitOfWorkFactory,UnitOfWorkFactory>();

    //注册记录仪
    container.RegisterSingle&所述; ILogger,NLogForUnitOfWork>();

    //注册所有的通用命令处理程序
    container.RegisterManyForOpenGeneric(typeof运算(ICommandHandler<>)
        AppDomain.CurrentDomain.GetAssemblies());

    container.RegisterDecorator(typeof运算(ICommandHandler<>)
        的typeof(TransactionCommandHandlerDecorator&其中;&GT));

    //注册将要使用的命令处理程序服务
    container.Register&所述; ISynchronisationService,SynchronisationService>();
    container.Register&所述; IPluginManagerService,PluginManagerService>();
}
 

以下线的期望的结果是创建整个构造的对象图有一个共享的UnitOfWork实例的对象:

  VAR处理器=解决< ICommandHandler< SyncExternalDataCommand>>();
 

下面是我的服务:

 公共类PluginManagerService:IPluginSettingsService
{
    公共PluginManagerService(IUnitOfWork的UnitOfWork)
    {
        this.unitOfWork =的UnitOfWork;
    }

    私人只读的UnitOfWork;

    无效IPluginSettingsService.RegisterPlugins()
    {
       //处理工作单元
    }
}

公共类SynchronisationService:ISynchronisationService
{
    公共PluginManagerService(IUnitOfWork的UnitOfWork)
    {
        this.unitOfWork =的UnitOfWork;
    }

    私人只读的UnitOfWork;

    无效ISynchronisationService.SyncData()
    {
       //处理工作单元
    }
}

公共类SyncExternalDataCommandHandler
    :ICommandHandler< SyncExternalDataCommand>
{
    ILogger记录;
    ISynchronisationService synchronisationService;
    IPluginManagerService pluginManagerService;

    公共SyncExternalDataCommandHandler(
        ISynchronisationService synchronisationService,
        IPluginManagerService pluginManagerService,
        ILogger记录仪)
    {
        this.synchronisationService = synchronisationService;
        this.pluginManagerService = pluginManagerService;
        this.logger =记录;
    }

    公共无效手柄(SyncExternalDataCommand命令)
    {
        //这里我不过通话双方的服务功能,以现在每
        //内部有不同的UnitOfWork参考,我们需要他们
        //很常见。
        this.synchronisationService.SyncData();
        this.pluginManagerService.RegisterPlugins();
    }
}
 

其中注册需要

解决方案

取决于应用程序的类型。既然你说的是两个不同的框架(MVC和WinForms),两者都将有不同的注册。

有关MVC应用程序(或Web应用程序的一般),最常见的做法是,以登记工作单元上的的每个Web请求的基础。例如,下面的注册将在单个Web请求期间缓存工作单位:

  container.Register< IUnitOfWork>(()=>
{
    VAR项目= HttpContext.Current.Items;

    VAR UOW =(IUnitOfWork)项目[的UnitOfWork];

    如果(UOW == NULL)
    {
        项目[的UnitOfWork] = UOW = container.GetInstance<的UnitOfWork>();
    }

    返回UOW;
});
 

该注册的缺点是,工作的单位没有设置(如果需要)。有的简单喷油器,增加了一个扩展包 RegisterPerWebRequest 扩展方法的容器,这将自动地确保实例设置在web请求的结束。使用这个包,你将能够做到以下报名方式:

  container.RegisterPerWebRequest< IUnitOfWork,的UnitOfWork>();
 

这是一个快捷方式:

  container.Register< IUnitOfWork,的UnitOfWork>(新WebRequestLifestyle());
 

一个Windows窗体另一方面应用,典型地是单线程(单个用户将使用该应用程序)。我认为这是不寻常有每个表单的工作,这是设置在窗体关闭的一个单元,但随着使用命令/处理模式,我觉得这是更好地采取了更多的服务导向的方法。我的意思是,这将是很好的设计它,你可以在业务层移动到WCF服务这样的方式,而不需要进行更改presentation层。你可以让你的命令只包含原语和(其他) DTO 小号实现这一目标。所以,不要实体框架实体保存到你的命令,因为这将使更难序列化的命令,它会导致意外以后。

当你做到这一点,那将是方便的命令处理程序之前创建一个新的工作单位开始执行,重复使用同一工作单位的处理程序的执行过程中,并提交它时,处理成功完成(且始终处理它)。这是一个典型的情况为<一href="http://simpleinjector.$c$cplex.com/wikipage?title=ObjectLifestyleManagement#PerLifetimeScope">Per终身范围生活方式。有<一个href="http://simpleinjector.$c$cplex.com/wikipage?title=ObjectLifestyleManagement#PerLifetimeScope">an扩展包,增加了 RegisterLifetimeScope 扩展方法的容器。使用这个包,你将能够做到以下报名方式:

  container.RegisterLifetimeScope&LT; IUnitOfWork,的UnitOfWork&GT;();
 

这是一个快捷方式:

  container.Register&LT; IUnitOfWork,的UnitOfWork&GT;(新LifetimeScopeLifestyle());
 

注册然而,故事的的一半。第二部分是决定何时保存工作单元的变化,以及在使用的寿命范围的生活方式,从哪里开始和结束这样的范围的情况下。既然你应该明确地开始了一生范围命令执行之前,并结束它,当命令执行完毕,要做到这一点的最好办法,就是通过使用命令处理程序装饰,可以换您的命令处理程序。因此,对于表单应用程序,您通常会注册一个额外的命令处理程序的装饰,它管理的寿命范围。这种方法不能在这种情况下工作。看看下面的装饰,但请注意的这是不正确的:

 私有类LifetimeScopeCommandHandlerDecorator&LT; T&GT;
    :ICommandHandler&LT; T&GT;
{
    私人只读集装箱货柜;
    私人只读ICommandHandler&LT; T&GT; decoratedHandler;

    公共LifetimeScopeCommandHandlerDecorator(...){...}

    公共无效手柄(T指令)
    {
        使用(this.container.BeginLifetimeScope())
        {
            // 错误!!!
            this.decoratedHandler.Handle(命令);
        }
    }
}
 

此方法的不能正常工作的,因为装修的命令处理程序之前创建的 的寿命范围启动。

我们也许会为试图解决这个问题,如下所示,但是这是不正确或者:

 使用(this.container.BeginLifetimeScope())
{
    //更不对!
    VAR处理器= this.container.GetInstance&LT; ICommandHandler&LT; T&GT;&GT;();

    handler.Handle(命令);
}
 

虽然请求了 ICommandHandler&LT; T&GT; 一辈子的范围上下文中,确实注入了 IUnitOfWork 的该范围,容器将返回时(再次)饰以 LifetimeScopeCommandHandlerDecorator&LT的处理程序; T&GT; 。呼叫 handler.Handle(命令)所以会造成一个递归调用,我们将最终有一个堆栈溢出异常。

这个问题是因为依赖关系图已经建成之前,我们就可以开始了一生的范围。因此,我们必须通过推迟构建图形的其余部分打破依赖图。要做到这一点,可以让你保持你的应用程序设计的清洁]最好的办法是通过改变装饰成一个代理,并注入工厂到它,这将创造,它应该来包装的类型。这样的 LifetimeScopeCommandHandlerProxy&LT; T&GT; 将是这样的:

  //这个类将是根组成部分
// Windows窗体应用程序
私有类LifetimeScopeCommandHandlerProxy&LT; T&GT; :ICommandHandler&LT; T&GT;
{
    //由于这种类型的组合物根的一部分,
    //我们允许在容器注入进去。
    私营集装箱货柜;
    私有函数功能:LT; ICommandHandler&LT; T&GT;&GT;厂;

    公共LifetimeScopeCommandHandlerProxy(集装箱货柜,
         FUNC&LT; ICommandHandler&LT; T&GT;&GT;厂)
    {
        this.factory =工厂;
        this.container =容器;
    }

    公共无效手柄(T指令)
    {
        使用(this.container.BeginLifetimeScope())
        {
            变种处理器= this.factory();

            handler.Handle(命令);
        }
    }
}
 

通过注入一个代表,我们可以延迟创建实例的时间,通过这样做,我们耽误的建设(其余)的依赖关系图。诀窍现在注册,而不是再次注射本身(当然),它会注入包装的情​​况下,这样的方式这个代理类。简单的喷油器支持注入 Func键&LT; T&GT; 工厂为装饰,这样你就可以简单地使用 RegisterDecorator 在这种情况下,即使在 RegisterSingleDecorator 扩展方法。

注意的顺序装饰(与此代理)注册(显然)的事项。由于该代理启动一个新的生命周期范围,应该换了提交工作单位的装饰。换句话说,一个更完整的注册应该是这样的:

  container.RegisterLifetimeScope&LT; IUnitOfWork,的UnitOfWork&GT;();

container.RegisterManyForOpenGeneric(
    的typeof(ICommandHandler&其中;&1+),
    AppDomain.CurrentDomain.GetAssemblies());

//注册处理节约的单元装饰器
//处理程序后,工作已成功执行。
//这个装饰将包装所有命令处理程序。
container.RegisterDecorator(
    的typeof(ICommandHandler&其中;&1+),
    的typeof(TransactionCommandHandlerDecorator&其中;&GT));

//注册启动一辈子范围代理。
//这个代理将包装交易装饰。
container.RegisterSingleDecorator(
    的typeof(ICommandHandler&其中;&1+),
    的typeof(LifetimeScopeCommandHandlerProxy&其中;&GT));
 

注册代理和装饰周围的其他方法将意味着 TransactionCommandHandlerDecorator&LT; T&GT; 将取决于不同的 IUnitOfWork 比依赖关系图的其他部分呢,这将意味着对工作单位取得该图中的所有更改将不会被提交。换句话说,你的应用程序将停止工作。因此,总是仔细阅读本登记。

祝你好运。

I have multiple services, each of which have a UnitOfWork injected into the constructor using the Simple Injector IoC container.

Currently I can see each UnitOfWork instance is a separate object, this is bad as i am using Entity Framework and require the same context reference across all units of work.

How can I ensure the same UnitOfWork instance is injected into all services per each resolve request? My UnitOfWor will be saved by an external command handler decorator after the command completes.

Please note, this is a common library and will be used for both MVC and Windows Forms, it would be nice to have a generic solution for both platforms if possible.

Code is below:

// snippet of code that registers types
void RegisterTypes()
{
    // register general unit of work class for use by majority of service layers
    container.Register<IUnitOfWork, UnitOfWork>();

    // provide a factory for singleton classes to create their own units of work 
    // at will
    container.RegisterSingle<IUnitOfWorkFactory, UnitOfWorkFactory>();

    // register logger
    container.RegisterSingle<ILogger, NLogForUnitOfWork>();

    // register all generic command handlers
    container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
        AppDomain.CurrentDomain.GetAssemblies());

    container.RegisterDecorator(typeof(ICommandHandler<>),
        typeof(TransactionCommandHandlerDecorator<>));

    // register services that will be used by command handlers
    container.Register<ISynchronisationService, SynchronisationService>();
    container.Register<IPluginManagerService, PluginManagerService>();
}

The desired outcome of the below line is to create a object which has a shared UnitOfWork instance throughout the constructed object graph:

var handler = Resolve<ICommandHandler<SyncExternalDataCommand>>();

Here are my services:

public class PluginManagerService : IPluginSettingsService
{
    public PluginManagerService(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
    }

    private readonly unitOfWork;

    void IPluginSettingsService.RegisterPlugins()
    {
       // manipulate the unit of work
    }
}

public class SynchronisationService : ISynchronisationService
{
    public PluginManagerService(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;
    }

    private readonly unitOfWork;

    void ISynchronisationService.SyncData()
    {
       // manipulate the unit of work
    }
}

public class SyncExternalDataCommandHandler
    : ICommandHandler<SyncExternalDataCommand>
{
    ILogger logger;
    ISynchronisationService synchronisationService;
    IPluginManagerService pluginManagerService;

    public SyncExternalDataCommandHandler(
        ISynchronisationService synchronisationService, 
        IPluginManagerService pluginManagerService, 
        ILogger logger)
    {
        this.synchronisationService = synchronisationService;
        this.pluginManagerService = pluginManagerService;
        this.logger = logger;
    }

    public void Handle(SyncExternalDataCommand command)
    {
        // here i will call both services functions, however as of now each
        // has a different UnitOfWork reference internally, we need them to 
        // be common.
        this.synchronisationService.SyncData();
        this.pluginManagerService.RegisterPlugins();
    }
}

解决方案

Which registration you need depends on the type of application. Since you are talking about two different frameworks (MVC and WinForms), both will have a different registration.

For an MVC application (or web applications in general), the most common thing to do is to register the unit of work on a per web request basis. For instance, the following registration will cache the unit of work during a single web request:

container.Register<IUnitOfWork>(() =>
{
    var items = HttpContext.Current.Items;

    var uow = (IUnitOfWork)items["UnitOfWork"];

    if (uow == null)
    {
        items["UnitOfWork"] = uow = container.GetInstance<UnitOfWork>();
    }

    return uow;
});

The downside of this registration is that the unit of work is not disposed (if needed). There is an extension package for the Simple Injector that adds RegisterPerWebRequest extension methods to the container, which will automatically ensure that the instance is disposed at the end of the web request. Using this package, you will be able to do the following registration:

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();

Which is a shortcut to:

container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle());

A Windows Forms application on the other hand, is typically single threaded (a single user will be using that application). I believe it is not unusual to have a single unit of work per form, which is disposed the form closes, but with the use of the command/handler pattern, I think it is better to take a more service oriented approach. What I mean by this is that it would be good to design it in such way that you can move the business layer to a WCF service, without the need to make changes to the presentation layer. You can achieve this by letting your commands only contain primitives and (other) DTOs. So don't store Entity Framework entities into your commands, because this will make serializing the command much harder, and it will lead to surprises later on.

When you do this, it would be convenient to create a new unit of work before the command handler starts executing, reuse that same unit of work during the execution of that handler, and commit it when the handler completed successfully (and always dispose it). This is a typical scenario for the Per Lifetime Scope lifestyle. There is an extension package that adds RegisterLifetimeScope extension methods to the container. Using this package, you will be able to do the following registration:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();

Which is a shortcut to:

container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle());

The registration however, is just half of the story. The second part is to decide when to save the changes of the unit of work, and in the case of the use of the Lifetime Scope lifestyle, where to start and end such a scope. Since you should explicitly start a lifetime scope before the command executes, and end it when the command finished executing, the best way to do this, is by using a command handler decorator, that can wrap your command handlers. Therefore, for the Forms Application, you would typically register an extra command handler decorator that manages the lifetime scope. This approach does not work in this case. Take a look at the following decorator, but please note that it is incorrect:

private class LifetimeScopeCommandHandlerDecorator<T>
    : ICommandHandler<T>
{
    private readonly Container container;
    private readonly ICommandHandler<T> decoratedHandler;

    public LifetimeScopeCommandHandlerDecorator(...) { ... }

    public void Handle(T command)
    {
        using (this.container.BeginLifetimeScope())
        {
            // WRONG!!!
            this.decoratedHandler.Handle(command);
        }
    }
}

This approach does not work, because the decorated command handler is created before the lifetime scope is started.

We might be tempted into trying to solve this problem as follows, but that isn't correct either:

using (this.container.BeginLifetimeScope())
{
    // EVEN MORE WRONG!!!
    var handler = this.container.GetInstance<ICommandHandler<T>>();

    handler.Handle(command);
}

Although requesting an ICommandHandler<T> inside the context of a lifetime scope, does indeed inject an IUnitOfWork for that scope, the container will return a handler that is (again) decorated with a LifetimeScopeCommandHandlerDecorator<T>. Calling handler.Handle(command) will therefore result in a recursive call and we'll end up with a stack overflow exception.

The problem is that the dependency graph is already built before we can start the lifetime scope. We therefore have to break the dependency graph by deferring building the rest of the graph. The best way to do this that allows you to keep your application design clean] is by changing the decorator into a proxy and injecting a factory into it that will create the type that it was supposed to wrap. Such LifetimeScopeCommandHandlerProxy<T> will look like this:

// This class will be part of the Composition Root of
// the Windows Forms application
private class LifetimeScopeCommandHandlerProxy<T> : ICommandHandler<T>
{
    // Since this type is part of the composition root,
    // we are allowed to inject the container into it.
    private Container container;
    private Func<ICommandHandler<T>> factory;

    public LifetimeScopeCommandHandlerProxy(Container container,
         Func<ICommandHandler<T>> factory)
    {
        this.factory = factory;
        this.container = container;
    }

    public void Handle(T command)
    {
        using (this.container.BeginLifetimeScope())
        {
            var handler = this.factory();

            handler.Handle(command);        
        }
    }
}

By injecting a delegate, we can delay the time the instance is created and by doing this we delay the construction of (the rest of) the dependency graph. The trick now is to register this proxy class in such way that it will inject the wrapped instances, instead of (of course) injecting itself again. Simple Injector supports injecting Func<T> factories into decorators, so you can simply use the RegisterDecorator and in this case even the RegisterSingleDecorator extension method.

Note that the order in which decorators (and this proxy) are registered (obviously) matters. Since this proxy starts a new lifetime scope, it should wrap the decorator that commits the unit of work. In other words, a more complete registration would look like this:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>),
    AppDomain.CurrentDomain.GetAssemblies());

// Register a decorator that handles saving the unit of
// work after a handler has executed successfully.
// This decorator will wrap all command handlers.
container.RegisterDecorator(
    typeof(ICommandHandler<>),
    typeof(TransactionCommandHandlerDecorator<>));

// Register the proxy that starts a lifetime scope.
// This proxy will wrap the transaction decorators.
container.RegisterSingleDecorator(
    typeof(ICommandHandler<>),
    typeof(LifetimeScopeCommandHandlerProxy<>));

Registering the proxy and decorator the other way around would mean that the TransactionCommandHandlerDecorator<T> would depend on a different IUnitOfWork than the rest of the dependency graph does, which would mean that all changes made to the unit of work in that graph will not get committed. In other words, your application will stop working. So always review this registration carefully.

Good luck.

这篇关于简单的注射器:在相同的图形服务注入相同的UnitOfWork实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆