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

g18*_*18c 8 .net c# dependency-injection entity-framework-4 simple-injector

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

目前我可以看到每个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>();
}
Run Code Online (Sandbox Code Playgroud)

下面一行的期望结果是创建一个对象,该对象在构造的对象图中具有共享的UnitOfWork实例:

var handler = Resolve<ICommandHandler<SyncExternalDataCommand>>();
Run Code Online (Sandbox Code Playgroud)

这是我的服务:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ven 20

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

对于MVC应用程序(或一般的Web应用程序),最常见的做法是基于每个Web请求注册工作单元.例如,以下注册将在单个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;
});
Run Code Online (Sandbox Code Playgroud)

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

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();
Run Code Online (Sandbox Code Playgroud)

这是一个快捷方式:

container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle());
Run Code Online (Sandbox Code Playgroud)

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

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

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();
Run Code Online (Sandbox Code Playgroud)

这是一个快捷方式:

container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle());
Run Code Online (Sandbox Code Playgroud)

然而,注册只是故事的一半.第二部分是决定何时保存工作单元的更改,并且在使用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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

    handler.Handle(command);
}
Run Code Online (Sandbox Code Playgroud)

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

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

// 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);        
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过注入一个委托,我们可以延迟创建实例的时间,并通过这样做,我们延迟依赖图的(其余部分)的构造.现在的诀窍是以这样的方式注册这个代理类,它将注入包装的实例,而不是(当然)再次注入自己.Simple Injector支持将Func<T>工厂注入装饰器,因此您可以简单地使用RegisterDecoratorand,在这种情况下甚至是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<>));
Run Code Online (Sandbox Code Playgroud)

反过来注册代理和装饰器意味着TransactionCommandHandlerDecorator<T>依赖于IUnitOfWork依赖图的其余部分所做的不同,这意味着对该图中的工作单元所做的所有更改都不会被提交.换句话说,您的应用程序将停止工作.所以请务必仔细阅读此注册.

祝好运.