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

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

问题描述

我有多个服务,每个服务都使用简单的方式将 UnitOfWork 注入到构造函数中注入器 IoC容器。

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

目前我可以看到每个 UnitOfWork 实例是一个单独的对象,这是不好,因为我正在使用实体框架,并且需要在所有工作单元中使用相同的上下文参考。

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.

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

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.

请注意,这是一个通用库并将用于MVC和Windows窗体,如果可能的话,为两个平台提供通用解决方案是很好的。

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.

代码如下:

// 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>();
}

下面的行的期望结果是创建一个共享的对象UnitOfWork实例遍及构造的对象图:

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>>();

这是我的服务:

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();
    }
}


推荐答案

需要注册取决于应用程序的类型。既然你在谈论两个不同的框架(MVC和WinForms),两者都会有不同的注册。

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.

对于MVC应用程序(或一般的Web应用程序),最多常见的事情是在每个Web请求基础。例如,以下注册将在单个Web请求中缓存工作单元:

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;
});

此注册的缺点是工作单位不处理(如果需要)。有扩展包对于简单注入器,将 RegisterPerWebRequest 扩展方法添加到容器中,这将自动确保实例在Web请求结束时处理。使用此软件包,您将可以执行以下注册:

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>();

哪个是快捷方式:

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

另一方面,Windows Forms应用程序通常是单线程(单个用户将使用应用)。我相信每个表单都有一个单独的工作单位,这是将表单关闭,但是使用命令/处理程序模式,我认为采取更加面向服务的方法是更好的办法。我的意思是,设计它的方式很好,可以将业务层移动到WCF服务,而不需要对表示层进行更改。您可以通过让您的命令仅包含原语和(其他) DTO 秒。所以不要将Entity Framework实体存储到你的命令中,因为这样可以使命令更加顺序化,这样会导致惊喜。

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.

当你这样做在命令处理程序开始执行之前创建一个新的工作单元是很方便的,在处理程序执行期间重新使用相同的工作单元,并在处理程序成功完成(并且始终处理它)时提交它。这是一个典型的情况:终身职业生涯。有扩展程序包 RegisterLifetimeScope 扩展方法添加到容器。使用此软件包,您将可以执行以下注册:

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>();

哪个是快捷方式:

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

然而,注册只是故事的一半。第二部分是决定何时保存工作单位的变更,在使用终身职业生涯的情况下,从哪里开始和结束这样的范围。因为你应该在命令执行之前明确地启动一个生命周期的范围,并且在命令执行结束时结束它,所以最好的方法是使用命令处理程序装饰器,它可以包装你的命令处理程序。因此,对于Forms应用程序,通常会注册一个额外的命令处理程序装饰器来管理生命周期范围。在这种情况下,这种方法不起作用。看看下面的装饰器,但请注意,这是不正确的:

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);
}

尽管请求一个 ICommandHandler< T> 在一个生命周期的上下文中,确实为该范围注入了一个 IUnitOfWork ,容器将返回一个(再次)用 LifetimeScopeCommandHandlerDecorator< 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.

问题是,在开始生命周期范围之前,依赖关系图已经构建。因此,我们必须通过延迟构建图的其余部分来破坏依赖图。执行此操作的最佳方法是允许您将应用程序设计保持清洁]是将装饰器更改为代理,并将工厂注入到该工厂中,以创建它应该包装的类型。这样的 LifetimeScopeCommandHandlerProxy< T> 将如下所示:

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);        
        }
    }
}

通过注入代理,我们可以延迟创建实例的时间,并且通过这样做,我们延迟依赖图的(其余部分)的构造。现在的诀窍是注册这个代理类,以便它将注入包装的实例,而不是(当然)再次注入自身。简单的注射器支持将 Func< T> 工厂注入装饰器,因此您可以简单地使用 RegisterDecorator ,在这种情况下甚至

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<>));

以相反的方式注册代理和装饰器将意味着 TransactionCommandHandlerDecorator< T> 将取决于与依赖图的其余部分不同的 IUnitOfWork ,这意味着对工作单位所做的所有更改该图不会承诺。换句话说,您的应用程序将停止工作。所以请仔细阅读此注册。

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.

祝你好运。

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

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