在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
创建的。
当您将DbContext
的RegisterSingleton
更改为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(及其注册)并更改生活方式将解决您的示例中的问题。
但是我希望你的应用程序最终会有一个IMemberService
, IUserService
, ICustomerService
等……所以你需要一个装饰器来支持每一个IXXXService
,如果你问我我不需要干 。 如果所有服务都将实现Save(List
您可以考虑创建一个开放的通用接口:
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甚至可以更好地使用命令/处理程序模式 (您应该真正阅读链接!)。 非常简短:在这种模式中,每个用例都被转换为一个消息对象(一个命令),它由一个命令处理程序处理,可以通过例如SaveChangesCommandHandlerDecorator
和ThreadScopedCommandHandlerDecorator
和ThreadScopedCommandHandlerDecorator
等进行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
和(特定)服务的需求。