我正在尝试在我的 Windows 窗体应用程序中实现 IoC。我的选择落在了 Simple Injector 上,因为它又快又轻。我还在我的应用程序中实现了工作单元和存储库模式。这是结构:
数据库上下文 :
public class MemberContext : DbContext
{
public MemberContext()
: base("Name=MemberContext")
{ }
public DbSet<Member> Members { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();\
}
}
型号 :
public class Member
{
public int MemberID { get; set; }
public string Name { get; set; }
}
通用存储库 :
public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class
{
internal DbContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
}
成员(member)资料库 :
public class MemberRepository : GenericRepository<Member>, IMemberRepository
{
public MemberRepository(DbContext context)
: base(context)
{ }
}
工作单位 :
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);
}
}
成员(member)服务 :
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> { member });
}
public void Save(List<Member> members)
{
members.ForEach(m =>
{
if (m.MemberID == default(int))
{
memberRepository.Insert(m);
}
});
unitOfWork.SaveChanges();
}
}
在成员(member)表格中,我只添加了一个文本框来输入成员(member)姓名和一个按钮来保存到数据库。这是成员(member)形式的代码:
frm成员(member) :
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);
}
}
我在 中实现了 SimpleInjector(引用 http://simpleinjector.readthedocs.org/en/latest/windowsformsintegration.html)程序.cs 如下面的代码所示:
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<IMemberRepository, MemberRepository>();
container.Register<IMemberService, MemberService>();
container.Register<DbContext, MemberContext>();
container.Register<IUnitOfWork, UnitOfWork>();
container.Verify();
}
}
当我运行程序并添加成员时,它不会保存到数据库中。如果我改了
container.Register
至 container.RegisterSingle
,它将保存到数据库。从文档中,RegisterSingle
将使我的类(class)成为单例。我不能使用 RegisterLifeTimeScope 因为它会产生一个错误“IMemberService 类型的注册委托(delegate)引发了异常。IUnitOfWork 注册为‘Lifetime Scope’生活方式,但在 Lifetime Scope 的上下文之外请求了该实例”
1) 如何在带有 UnitOfWork 和 Repository 模式的 Windows 窗体中使用 SimpleInjector?
2)我是否正确实现了模式?
最佳答案
您遇到的问题是您的服务、存储库、工作单元和 dbcontext 之间的生活方式不同。
因为MemberRepository
有一种单例生活方式,Simple Injector 将创建一个实例,该实例将在应用程序的持续时间内重复使用,对于 WinForms 应用程序,这可能是几天,甚至几周或几个月。注册 MemberRepository
的直接后果因为 Singleton 是这个类的所有依赖项也将成为 Singletons,无论在注册中使用什么生活方式。这是一个常见的问题,称为 Captive Dependency .
作为旁注:diagnostic services Simple Injector 能够发现这个配置错误,并会显示/抛出 Potential Lifestyle Mismatch warning .
所以MemberRepository
是 Singleton 并且有一个相同的 DbContext
在整个应用程序生命周期中。但是UnitOfWork
,它也依赖于 DbContext
将收到 DbContext
的不同实例,因为注册为DbContext
是 transient 。在您的示例中,此上下文永远不会保存新创建的 Member
因为这个 DbContext
没有任何新创建的 Member
,该成员是在不同的 DbContext
中创建的.
当您更改DbContext
的注册时至 RegisterSingleton
它将开始工作,因为现在每个服务、类或任何取决于 DbContext
将获得相同的实例。
但这肯定是不是 解决方案是因为有一个 DbContext
您可能已经知道,在应用程序的整个生命周期内都会遇到麻烦。这在 post 中有详细解释。 .
您需要的解决方案是使用 DbContext
的 Scoped 实例。 ,您已经尝试过。您缺少一些有关如何使用 Simple Injector(以及大多数其他容器)的生命周期范围功能的信息。使用 Scoped 生活方式时,必须有一个事件范围,因为异常消息明确指出。启动生命周期范围非常简单:
using (ThreadScopedLifestyle.BeginScope(container))
{
// all instances resolved within this scope
// with a ThreadScopedLifestyleLifestyle
// will be the same instance
}
您可以详细阅读here .
将注册更改为:
var container = new Container();
container.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();
container.Register<IMemberRepository, MemberRepository>(Lifestyle.Scoped);
container.Register<IMemberService, MemberService>(Lifestyle.Scoped);
container.Register<DbContext, MemberContext>(Lifestyle.Scoped);
container.Register<IUnitOfWork, UnitOfWork>(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<IMemberService>();
memberService.Save(member);
}
}
基本上就是你所需要的。
但是我们现在引入了一个新问题。我们现在正在使用 Service Locator anti pattern获取
IMemberService
的作用域实例实现。因此,我们需要一些基础设施对象来作为 Cross-Cutting Concern 为我们处理这个问题。在应用程序中。 Decorator是实现这一点的完美方式。另见 here .这将如下所示:public class ThreadScopedMemberServiceDecorator : IMemberService
{
private readonly Func<IMemberService> decorateeFactory;
private readonly Container container;
public ThreadScopedMemberServiceDecorator(Func<IMemberService> decorateeFactory,
Container container)
{
this.decorateeFactory = decorateeFactory;
this.container = container;
}
public void Save(List<Member> members)
{
using (ThreadScopedLifestyle.BeginScope(container))
{
IMemberService service = this.decorateeFactory.Invoke();
service.Save(members);
}
}
}
您现在将其注册为 Simple Injector 中的(单例)装饰器
Container
像这样:container.RegisterDecorator(
typeof(IMemberService),
typeof(ThreadScopedMemberServiceDecorator),
Lifestyle.Singleton);
容器将提供一个依赖于
IMemberService
的类。有了这个 ThreadScopedMemberServiceDecorator
.在这个容器中将注入(inject)一个 Func<IMemberService>
当调用时,它将使用配置的生活方式从容器返回一个实例。添加此装饰器(及其注册)并更改生活方式将解决您示例中的问题。
但是,我希望您的应用程序最终会有一个
IMemberService
, IUserService
, ICustomerService
, 等等... 所以你需要为每一个 IXXXService
一个装饰器,不太DRY如果你问我。如果所有服务都将实现 Save(List<T> items)
您可以考虑创建一个开放的通用接口(interface):public interface IService<T>
{
void Save(List<T> items);
}
public class MemberService : IService<Member>
{
// same code as before
}
您可以使用 Batch-Registration 在一行中注册所有实现:
container.Register(typeof(IService<>),
new[] { Assembly.GetExecutingAssembly() },
Lifestyle.Scoped);
您可以将所有这些实例包装到上述
ThreadScopedServiceDecorator
的单个开放通用实现中。 .如果使用 command / handler pattern,IMO 会更好(你真的应该阅读链接!)对于这种类型的工作。简而言之:在这种模式中,每个 use case被转换为由单个命令处理程序处理的消息对象(命令),可以通过例如装饰
SaveChangesCommandHandlerDecorator
和一个 ThreadScopedCommandHandlerDecorator
和 LoggingDecorator
等等。您的示例将如下所示:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
public class CreateMemberCommand
{
public string MemberName { get; set; }
}
使用以下处理程序:
public class CreateMemberCommandHandler : ICommandHandler<CreateMemberCommand>
{
//notice that the need for MemberRepository is zero IMO
private readonly IGenericRepository<Member> memberRepository;
public CreateMemberCommandHandler(IGenericRepository<Member> memberRepository)
{
this.memberRepository = memberRepository;
}
public void Handle(CreateMemberCommand command)
{
var member = new Member { Name = command.MemberName };
this.memberRepository.Insert(member);
}
}
public class SaveChangesCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private ICommandHandler<TCommand> decoratee;
private DbContext db;
public SaveChangesCommandHandlerDecorator(
ICommandHandler<TCommand> decoratee, DbContext db)
{
this.decoratee = decoratee;
this.db = db;
}
public void Handle(TCommand command)
{
this.decoratee.Handle(command);
this.db.SaveChanges();
}
}
并且表单现在可以依赖于
ICommandHandler<T>
:public partial class frmMember : Form
{
private readonly ICommandHandler<CreateMemberCommand> commandHandler;
public frmMember(ICommandHandler<CreateMemberCommand> 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
和一个(特定的)服务。
关于c# - 在 Windows 窗体中使用带有工作单元和存储库模式的简单注入(inject)器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29802957/