Simple Injector:在同一个图的服务中注入相同的UnitOfWork实例

时间:2012-06-08 19:38:25

标签: c# .net entity-framework-4 dependency-injection simple-injector

我有多个服务,每个服务都使用Simple Injector IoC容器将UnitOfWork注入到构造函数中。

目前我可以看到每个UnitOfWork实例都是一个单独的对象,这很糟糕,因为我使用的是Entity Framework,并且需要在所有工作单元中使用相同的上下文引用。

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

请注意,这是一个公共库,将用于MVC和Windows Forms,如果可能的话,为两个平台提供通用解决方案会很不错。

代码如下:

// 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实例的对象:

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

1 个答案:

答案 0 :(得分:20)

您需要哪种注册取决于应用程序的类型。由于您正在讨论两种不同的框架(MVC和WinForms),两者都会有不同的注册。

对于MVC应用程序(或一般的Web应用程序),最常见的事情是在per web request basis上注册工作单元。例如,以下注册将在单个Web请求期间缓存工作单元:

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

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

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

    return uow;
});

此注册的缺点是不处理工作单元(如果需要)。 Simple Injector有an extension package,它将RegisterPerWebRequest扩展方法添加到容器中,这将自动确保实例在Web请求结束时处理。使用此软件包,您将能够进行以下注册:

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();

以下是:

的快捷方式
container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle());

另一方面,Windows窗体应用程序通常是单线程的(单个用户将使用该应用程序)。我相信每个表单有一个单独的工作单元并不常见,表单关闭,但是使用命令/处理程序模式,我认为采用更加面向服务的方法更好。我的意思是,以这样的方式设计它,以便您可以将业务层移动到WCF服务,而无需更改表示层。您可以通过让命令仅包含基元和(其他)DTO来实现此目的。因此,不要将Entity Framework实体存储到您的命令中,因为这会使命令序列化更加困难,并且稍后会导致意外。

执行此操作时,在命令处理程序开始执行之前创建新的工作单元会很方便,在执行该处理程序期间重用相同的工作单元,并在处理程序成功完成时提交它(并且始终处理它)。这是Per Lifetime Scope lifestyle的典型场景。有an extension packageRegisterLifetimeScope扩展方法添加到容器中。使用此软件包,您将能够进行以下注册:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();

以下是:

的快捷方式
container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle());

然而,注册只是故事的一半。第二部分是决定何时保存工作单元的更改,并且在使用Lifetime Scope生活方式的情况下,从哪里开始和结束这样的范围。由于您应该在命令执行之前显式启动生命周期范围,并在命令完成执行时结束它,最好的方法是使用命令处理程序装饰器,它可以包装您的命令处理程序。因此,对于Forms应用程序,通常会注册一个额外的命令处理程序装饰器来管理生命周期范围。在这种情况下,这种方法不起作用。看看下面的装饰者,但请注意它是不正确的

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

此方法不起作用,因为在生命周期范围开始之前创建了修饰的命令处理程序。

我们可能会尝试按如下方式尝试解决此问题,但这也不正确:

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

    handler.Handle(command);
}

虽然在生命周期范围的上下文中请求ICommandHandler<T>,但确实为该范围注入IUnitOfWork,容器将返回一个(再次)用{{1}装饰的处理程序}。因此,调用LifetimeScopeCommandHandlerDecorator<T>将导致递归调用,并且最终会出现堆栈溢出异常。

问题是在我们可以启动生命周期范围之前已经构建了依赖图。因此,我们必须通过推迟构建图的其余部分来打破依赖图。执行此操作以使您的应用程序设计保持清洁的最佳方法是将装饰器更改为代理并将工厂注入其中,以创建它应该包装的类型。这样的handler.Handle(command)将如下所示:

LifetimeScopeCommandHandlerProxy<T>

通过注入委托,我们可以延迟创建实例的时间,并且通过这样做,我们可以延迟依赖图的(其余部分)的构造。现在的诀窍是以这样的方式注册这个代理类,它将注入包装的实例,而不是(当然)再次注入自己。 Simple Injector支持将// 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扩展方法。

请注意,装饰器(和此代理)的注册顺序(显然)很重要。由于此代理启动了新的生命周期范围,因此它应该包装提交工作单元的装饰器。换句话说,更完整的注册将如下所示:

RegisterSingleDecorator

反过来注册代理和装饰器意味着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>,这意味着对该单元所做的所有更改该图表中的工作将不会被提交。换句话说,您的应用程序将停止工作。因此,请务必仔细阅读此注册。

祝你好运。