在Windows窗体中使用带有工作单元和存储库模式的简单注入器

我正在尝试在我的Windows窗体应用程序中实现IoC。 我的选择落在Simple Injector上,因为它快速而轻巧。 我还在我的应用程序中实现了工作单元和存储库模式。 这是结构:

DbContext

public class MemberContext : DbContext { public MemberContext() : base("Name=MemberContext") { } public DbSet Members { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Conventions.Remove();\ } } 

型号

 public class Member { public int MemberID { get; set; } public string Name { get; set; } } 

GenericRepository

 public abstract class GenericRepository : IGenericRepository where TEntity : class { internal DbContext context; internal DbSet dbSet; public GenericRepository(DbContext context) { this.context = context; this.dbSet = context.Set(); } public virtual void Insert(TEntity entity) { dbSet.Add(entity); } } 

MemberRepository

 public class MemberRepository : GenericRepository, IMemberRepository { public MemberRepository(DbContext context) : base(context) { } } 

UnitOfWork

 public class UnitOfWork : IUnitOfWork { public DbContext context; public UnitOfWork(DbContext context) { this.context = context; } public void SaveChanges() { context.SaveChanges(); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } 

会员服务

 public class MemberService : IMemberService { private readonly IUnitOfWork unitOfWork; private readonly IMemberRepository memberRepository; public MemberService(IUnitOfWork unitOfWork, IMemberRepository memberRepository) { this.unitOfWork = unitOfWork; this.memberRepository = memberRepository; } public void Save(Member member) { Save(new List { member }); } public void Save(List members) { members.ForEach(m => { if (m.MemberID == default(int)) { memberRepository.Insert(m); } }); unitOfWork.SaveChanges(); } } 

在成员表单中,我只添加一个文本框输入成员名称和一个保存到数据库的按钮。 这是成员forms的代码:

frmMember

 public partial class frmMember : Form { private readonly IMemberService memberService; public frmMember(IMemberService memberService) { InitializeComponent(); this.memberService = memberService; } private void btnSave_Click(object sender, EventArgs e) { Member member = new Member(); member.Name = txtName.Text; memberService.Save(member); } } 

我在Program.cs中实现SimpleInjector(请参阅http://simpleinjector.readthedocs.org/en/latest/windowsformsintegration.html ),如下面的代码所示:

 static class Program { private static Container container; [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Bootstrap(); Application.Run(new frmMember((MemberService)container.GetInstance(typeof(IMemberService)))); } private static void Bootstrap() { container = new Container(); container.RegisterSingle(); container.Register(); container.Register(); container.Register(); container.Verify(); } } 

当我运行程序并添加一个成员时,它不会保存到数据库。 如果我将container.Register更改为container.RegisterSingle ,它将保存到数据库。 从文档中, RegisterSingle将使我的类成为Singleton。 我无法使用RegisterLifeTimeScope,因为它会产生错误

“类型IMemberService的注册委托引发了exception.IUnitOfWork注册为’Lifetime Scope’生活方式,但实例是在终身范围的上下文之外请求的”

1)如何在Windows窗体中使用带有UnitOfWork和Repository模式的SimpleInjector?
2)我是否正确实施了模式?

您遇到的问题是您的服务,存储库,unitofwork和dbcontext之间的生活方式存在差异。

由于MemberRepository具有Singleton生活方式,Simple Injector将创建一个将在应用程序持续时间内重用的实例,使用WinForms应用程序可能需要几天甚至几周或几个月。 将MemberRepository注册为Singleton的直接后果是,无论注册中使用何种生活方式,此类的所有依赖项都将成为Singletons。 这是一个称为Captive Dependency的常见问题。

作为旁注: Simple Injector的诊断服务能够发现此配置错误并显示/抛出潜在的生活方式不匹配警告 。

因此, MemberRepository是Singleton,并且在整个应用程序生命周期中都有一个相同的DbContext 。 但是对DbContext具有依赖性的UnitOfWork将接收DbContext的不同实例,因为DbContext的注册是Transient。 在您的示例中,此上下文将永远不会保存新创建的Member因为此DbContext没有任何新创建的Member ,该成员是在不同的DbContext创建的。

当您将DbContextRegisterSingleton更改为RegisterSingleton ,它将开始工作,因为现在每个服务,类或任何依赖于DbContext都将获得相同的实例。

但这肯定不是解决方案,因为在应用程序的生命周期中有一个DbContext会让你陷入困境,正如你可能已经知道的那样。 本文将详细解释这一点。

您需要的解决方案是使用已经尝试过的DbContext的Scoped实例。 您缺少一些有关如何使用Simple Injector(以及其他大多数容器)的生命周期范围function的信息。 当使用Scoped生活方式时,必须有一个活动范围,因为exception消息明确指出。 启动生命周期范围非常简单:

 using (ThreadScopedLifestyle.BeginScope(container)) { // all instances resolved within this scope // with a ThreadScopedLifestyleLifestyle // will be the same instance } 

你可以在这里详细阅读。

将注册更改为:

 var container = new Container(); container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle(); container.Register(Lifestyle.Scoped); container.Register(Lifestyle.Scoped); container.Register(Lifestyle.Scoped); container.Register(Lifestyle.Scoped); 

并将代码从btnSaveClick()更改为:

 private void btnSave_Click(object sender, EventArgs e) { Member member = new Member(); member.Name = txtName.Text; using (ThreadScopedLifestyle.BeginScope(container)) { var memberService = container.GetInstance(); memberService.Save(member); } } 

基本上是你需要的。

我们现在引入了一个新问题。 我们现在使用Service Locator反模式来获取IMemberService实现的Scoped实例。 因此,我们需要一些基础设施对象,它将为我们处理这个问题 ,作为应用程序中的交叉关注点 。 装饰器是实现这一目标的完美方式。 另见这里 。 这看起来像:

 public class ThreadScopedMemberServiceDecorator : IMemberService { private readonly Func decorateeFactory; private readonly Container container; public ThreadScopedMemberServiceDecorator(Func decorateeFactory, Container container) { this.decorateeFactory = decorateeFactory; this.container = container; } public void Save(List members) { using (ThreadScopedLifestyle.BeginScope(container)) { IMemberService service = this.decorateeFactory.Invoke(); service.Save(members); } } } 

您现在将此注册为Simple Injector Container中的(Singleton)装饰器,如下所示:

 container.RegisterDecorator( typeof(IMemberService), typeof(ThreadScopedMemberServiceDecorator), Lifestyle.Singleton); 

容器将提供一个依赖于使用此ThreadScopedMemberServiceDecorator 。 在这个容器中将注入一个Func ,当被调用时,它将使用配置的生活方式从容器中返回一个实例。

添加此Decorator(及其注册)并更改生活方式将解决您的示例中的问题。

但是我希望你的应用程序最终会有一个IMemberServiceIUserServiceICustomerService等……所以你需要一个装饰器来支持每一个IXXXService ,如果你问我我不需要干 。 如果所有服务都将实现Save(List items)您可以考虑创建一个开放的通用接口:

 public interface IService { void Save(List items); } public class MemberService : IService { // same code as before } 

您使用批量注册在一行中注册所有实现:

 container.Register(typeof(IService<>), new[] { Assembly.GetExecutingAssembly() }, Lifestyle.Scoped); 

您可以将所有这些实例包装到上述ThreadScopedServiceDecorator的单个开放generics实现中。

对于这种类型的工作,IMO甚至可以更好地使用命令/处理程序模式 (您应该真正阅读链接!)。 非常简短:在这种模式中,每个用例都被转换为一个消息对象(一个命令),它由一个命令处理程序处理,可以通过例如SaveChangesCommandHandlerDecoratorThreadScopedCommandHandlerDecoratorThreadScopedCommandHandlerDecorator等进行ThreadScopedCommandHandlerDecorator

您的示例将如下所示:

 public interface ICommandHandler { void Handle(TCommand command); } public class CreateMemberCommand { public string MemberName { get; set; } } 

使用以下处理程序:

 public class CreateMemberCommandHandler : ICommandHandler { //notice that the need for MemberRepository is zero IMO private readonly IGenericRepository memberRepository; public CreateMemberCommandHandler(IGenericRepository memberRepository) { this.memberRepository = memberRepository; } public void Handle(CreateMemberCommand command) { var member = new Member { Name = command.MemberName }; this.memberRepository.Insert(member); } } public class SaveChangesCommandHandlerDecorator : ICommandHandler { private ICommandHandler decoratee; private DbContext db; public SaveChangesCommandHandlerDecorator( ICommandHandler decoratee, DbContext db) { this.decoratee = decoratee; this.db = db; } public void Handle(TCommand command) { this.decoratee.Handle(command); this.db.SaveChanges(); } } 

表单现在可以依赖于ICommandHandler

 public partial class frmMember : Form { private readonly ICommandHandler commandHandler; public frmMember(ICommandHandler commandHandler) { InitializeComponent(); this.commandHandler = commandHandler; } private void btnSave_Click(object sender, EventArgs e) { this.commandHandler.Handle( new CreateMemberCommand { MemberName = txtName.Text }); } } 

这些都可以注册如下:

 container.Register(typeof(IGenericRepository<>), typeof(GenericRepository<>)); container.Register(typeof(ICommandHandler<>), new[] { Assembly.GetExecutingAssembly() }); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(SaveChangesCommandHandlerDecorator<>)); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ThreadScopedCommandHandlerDecorator<>), Lifestyle.Singleton); 

此设计将完全消除对UnitOfWork和(特定)服务的需求。