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

我有多个服务,每个服务都使用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(); // provide a factory for singleton classes to create their own units of work // at will container.RegisterSingle(); // register logger container.RegisterSingle(); // 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(); container.Register(); } 

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

 var handler = Resolve<ICommandHandler>(); 

这是我的服务:

 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 { 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),因此两者都会有不同的注册。

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

 container.Register(() => { var items = HttpContext.Current.Items; var uow = (IUnitOfWork)items["UnitOfWork"]; if (uow == null) { items["UnitOfWork"] = uow = container.GetInstance(); } return uow; }); 

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

 container.RegisterPerWebRequest(); 

这是一个快捷方式:

 container.Register(new WebRequestLifestyle()); 

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

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

 container.RegisterLifetimeScope(); 

这是一个快捷方式:

 container.Register(new LifetimeScopeLifestyle()); 

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

 private class LifetimeScopeCommandHandlerDecorator : ICommandHandler { private readonly Container container; private readonly ICommandHandler 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>(); handler.Handle(command); } 

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

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

 // This class will be part of the Composition Root of // the Windows Forms application private class LifetimeScopeCommandHandlerProxy : ICommandHandler { // Since this type is part of the composition root, // we are allowed to inject the container into it. private Container container; private Func> factory; public LifetimeScopeCommandHandlerProxy(Container container, Func> 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工厂注入装饰器,因此您只需使用RegisterDecorator ,在这种情况下甚至可以使用RegisterSingleDecorator扩展方法。

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

 container.RegisterLifetimeScope(); 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<>)); 

IUnitOfWork注册代理和装饰器意味着TransactionCommandHandlerDecorator将依赖于与依赖图的其余部分不同的IUnitOfWork ,这意味着对该图中的工作单元所做的所有更改都将无法获得承诺。 换句话说,您的应用程序将停止工作。 所以请务必仔细阅读此注册。

祝好运。