8

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

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

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

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

代码如下:

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

1 回答 1

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

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

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>();

这是一个快捷方式:

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

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

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

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();

这是一个快捷方式:

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

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

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>在生命周期范围内请求 an ,确实IUnitOfWork为该范围注入了 an ,但容器将返回一个(再次)用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);        
        }
    }
}

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

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

祝你好运。

于 2012-06-09T14:55:55.517 回答