使用简单的注射器与工作单位Windows窗体中的存储库模式 [英] Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form

查看:241
本文介绍了使用简单的注射器与工作单位Windows窗体中的存储库模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在Windows窗体应用程序中实现IoC。我的选择落在了简单的注射器上,因为它是快速和轻便的。我也在我的应用程序中实现工作单元和存储库模式。以下是结构:



DbContext

  public class MemberContext:DbContext 
{
public MemberContext()
:base(Name = MemberContext)
{}

public DbSet< ;会员>会员{get;组;

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Conventions.Remove< PluralizingTableNameConvention>(); \
}
}

模型

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

GenericRepository

  public abstract class GenericRepository< TEntity> :IGenericRepository< TEntity> 
其中TEntity:class
{
internal DbContext context;
内部DbSet< TEntity> dbSet;

public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set< TEntity>();
}

public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
}

MemberRepository p>

  public class MemberRepository:GenericRepository< Member>,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 boolwhere = false;

protected virtual void Dispose(bool disposal)
{
if(!this.disposed)
{
if(disposal)
{
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保存(成员)
{
保存(新列表<成员> {member});
}

public void Save(List< Member> members)
{
members.ForEach(m =>
{
if (m.MemberID == default(int))
{
memberRepository.Insert(m);
}
});
unitOfWork.SaveChanges();
}
}

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



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

我实现了SimpleInjector(参考 http://simpleinjector.readthedocs.org/en/latest/windowsformsintegration.html )在 Program.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 将使我的课程成为Singleton。我不能使用RegisterLifeTimeScope,因为它会产生一个错误



类型为IMemberService的注册代表抛出一个异常,IUnitOfWork被注册为Lifetime Scope生活方式,但是该实例在终身范围的上下文之外被请求



1)如何在Windows窗体中使用SimpleInjector与UnitOfWork&存储库模式?

2)我是否正确地实现模式?

解决方案

您的服务,存储库,工作单元和dbcontext之间的生活方式的差异。



由于 MemberRepository 具有单身生活,Simple Injector将创建一个实例,该实例将在应用程序,可以是WinForms应用程序的几天甚至几周或几个月。将 MemberRepository 注册为单例的直接后果是,无论在注册中使用了什么生活方式,此类的所有依赖关系都将变为单例。这些依赖关系称为俘虏依赖关系



作为附注: 诊断服务能够发现此配置错误,并显示/抛出一个潜在的生活不情愿警告



所以 MemberRepository 是单例,并且在整个应用程序生命周期中都有一个和相同的DbContext。但是,与DbContext有关系的UnitOfWork会收到DbContext的不同实例,因为DbContext的注册是暂时的。在这个例子中,这个上下文将永远不会保存新创建的成员,因为这个DbContext没有任何新创建的成员 ,该成员在不同的DbContext中创建。



当您将DbContext的注册更改为 RegisterSingle 时,它将开始工作,因为现在每个服务,类或任何其他取决于DbContext将获得相同的实例。



但这肯定是不是的解决方案,因为在应用程序的一生中拥有一个DbContext会让您陷入麻烦,因为您可能已经知道。这在帖子中有很详细的解释。



您需要的解决方案是使用已经尝试的DbContext的范围实例。您缺少有关如何使用简单注射器的生命周期特征(以及其中大部分其他容器)的一些信息。当使用范围生命周期时,必须有一个有效的生命周期范围,因为异常消息清楚地说明。开始一生的范围很简单:

  using(container.BeginLifetimeScope())
{
/ /所有实例在此范围内解决
//与LifetimeScope生活方式
//将是同一个实例
}

您可以详细阅读这里



将注册更改为:

  container.RegisterLifetimeScope< IMemberRepository,MemberRepository>(); 
container.RegisterLifetimeScope< IMemberService,MemberService>();
container.RegisterLifetimeScope< DbContext,MemberContext>();
container.RegisterLifetimeScope< IUnitOfWork,UnitOfWork>();

并从更改代码btnSaveClick()到:

  private void btnSave_Click(object sender,EventArgs e)
{
Member member = new Member ();
member.Name = txtName.Text;

using(container.BeginLifetimeScope())
{
var memberService = container.GetInstance< IMemberService>();
memberService.Save(member);
}
}

基本上是你需要的。



但是我们现在介绍了一个新的问题。我们正在使用服务定位器反模式获取范围实施 IMemberService 实现。因此,我们需要一些基础设施对象,将作为跨领域关注应用程序。 装饰器是实现此目的的完美方式。另见 here 。这将看起来像:

  public class LifetimeScopedMemberServiceDecorator:IMemberService 
{
private readonly Func< IMemberService> decorateeFactory;
私人只读容器容器;

public LifetimeScopedMemberServiceDecorator(Func< IMemberService> decorateeFactory,
容器容器)
{
this.decorateeFactory = decorateeFactory;
this.container = container;
}

public void Save(List< Member> members)
{
using(this.container.BeginLifetimeScope())
{
IMemberService service = this.decorateeFactory.Invoke();

service.Save(members);
}
}
}

您现在注册为(单身)装饰器在简单的注射器容器中,如下所示:

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

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



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



我希望你的应用程序最终会有一个IMemberService,IUserService,ICustomerService等...所以你需要一个装饰器,每个IXXXService,不是很如果你问我, DRY 。如果所有服务都将实现 Save(List< T> items),您可以考虑创建一个打开的通用界面:

  public interface IService< T> 
{
void Save(List< T> items);
}

public class MemberService:IService< Member>
{
//与以前相同的代码
}

你使用批次注册在一行中注册所有实现:

  container.RegisterManyForOpenGeneric(typeof(IService<)),
new LifetimeScopeLifestyle(),
装配.GetExecutingAssembly());

您可以将所有这些实例包装到上述 LifetimeScopeServiceDecorator



IMO甚至会更好地使用命令/处理程序模式(你应该真正阅读链接!)这种类型的工作。很简单:在这种模式中,每个用例都被转换为消息对象(命令)它由单个命令处理程序处理,可以通过例如装饰一个 SaveChangesCommandHandlerDecorator 和一个 LifetimeScopeCommandHandlerDecorator LoggingDecorator 等等。



您的示例将如下所示:

  public interface ICommandHandler< TCommand> 
{
void Handle(TCommand命令);
}

public class CreateMemberCommand
{
public string MemberName {get;组; }
}

public class CreateMemberCommandHandler:ICommandHandler< CreateMemberCommand>
{
//注意对MemberRepository的需求为零IMO
private readonly IGenericRepository< Member> memberRepository;

public CreateMemberCommandHandler(IGenericRepository< Member> memberRepository)
{
this.memberRepository = memberRepository;
}

public void Handle(CreateMemberCommand命令)
{
var member = new Member {Name = command.MemberName};
this.memberRepository.Insert(member);
}
}

public class SaveChangesCommandHandlerDecorator< TCommand>
:ICommandHandler< TCommand>
{
私人ICommandHandler< TCommand> decoratee;
private DbContext db;

public SaveChangesCommandHandlerDecorator(
ICommandHandler< TCommand> decoratee,DbContext db)
{
this.decoratee = decoratee;
this.db = db;
}

public void Handle(TCommand命令)
{
this.decoratee.Handle(command);
this.db.SaveChanges();
}
}

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

注册为:

  // Simple Injector v3.x 
container.Register(typeof(IGenericRepository)),
typeof(GenericRepository<> );
container.Register(typeof(ICommandHandler<)),
new [] {Assembly.GetExecutingAssembly()});
container.RegisterDecorator(typeof(ICommandHandler<)),
typeof(SaveChangesCommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<)),
typeof(LifetimeScopedCommandHandlerDecorator),
Lifestyle.Singleton);

//简单注入器v2.x
container.RegisterOpenGeneric(typeof(IGenericRepository),
typeof(GenericRepository));
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<)),
Assembly.GetExecutingAssembly());
container.RegisterDecorator(typeof(ICommandHandler<)),
typeof(SaveChangesCommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<)),
typeof(LifetimeScopedCommandHandlerDecorator),
Lifestyle.Singleton);

此设计将消除对 UnitOfWork 和(具体)服务。


I'm trying to implement IoC in my windows form application. My choice fell on Simple Injector, because it's fast and lightweight. I also implement unit of work and repository pattern in my apps. Here is the structure:

DbContext:

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

Model:

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

GenericRepository:

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

MemberRepository:

public class MemberRepository : GenericRepository<Member>, 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);
    }
}

MemberService:

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

In Member Form I only add a textbox to input member name and a button to save to database. This is the code in member form:

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

I implement the SimpleInjector (refer to http://simpleinjector.readthedocs.org/en/latest/windowsformsintegration.html) in Program.cs as seen in the code below:

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

When I run the program and add a member, it doesn't save to database. If I changed container.Register to container.RegisterSingle, it will save to database. From the documentation, RegisterSingle will make my class to be a Singleton. I can't using RegisterLifeTimeScope because it will generate an error

"The registered delegate for type IMemberService threw an exception. The IUnitOfWork is registered as 'Lifetime Scope' lifestyle, but the instance is requested outside the context of a Lifetime Scope"

1) How to use SimpleInjector in Windows Form with UnitOfWork & Repository pattern?
2) Do I implement the patterns correctly?

解决方案

The problem you have is the difference in lifestyles between your service, repository, unitofwork and dbcontext.

Because the MemberRepository has a singleton lifetime Simple Injector will create one instance which will be reused for the lifetime of the application, which could be days, even weeks or months with a WinForms application. The direct consequence from registering the MemberRepository as singleton is that all dependencies of this class will become singleton as well, no matter what lifestyle is used in the registration. These dependencies are called ‘captive dependencies’.

As a side note: The diagnostic services of Simple Injector are able to spot this configuration mistake and will show/throw a Potential Lifestyle Mismatch warning.

So the MemberRepository is singleton and has one and the same DbContext throughout the application lifetime. But the UnitOfWork, which has a dependency also on DbContext will receive a different instance of the DbContext, because the registration for DbContext is transient. This context will, in your example, never save the newly created Member because this DbContext does not have any newly created Member, the member is created in a different DbContext.

When you change the registration of DbContext to RegisterSingle it will start working, because now every service, class or whatever depending on DbContext will get the same instance.

But this is certainly not the solution because having one DbContext for the lifetime of the application will get you into trouble, as you probably already know. This is explained in great detail in this post.

The solution you need is using a scoped instance of the DbContext, which you already tried. You are missing some information on how to use the lifetime scope feature of Simple Injector (and most of the other containers out there). When using a scoped lifetime there must be an active lifetime scope as the exception message clearly states. Starting a lifetime scope is pretty simple:

using (container.BeginLifetimeScope()) 
{
    // all instances resolved within this scope
    // with a LifetimeScope Lifestyle
    // will be the same instance
}

You can read in detail here.

Changing the registrations to:

container.RegisterLifetimeScope<IMemberRepository, MemberRepository>();
container.RegisterLifetimeScope<IMemberService, MemberService>();
container.RegisterLifetimeScope<DbContext, MemberContext>();
container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>();

and changing the code from btnSaveClick() to:

private void btnSave_Click(object sender, EventArgs e)
{
    Member member = new Member();
    member.Name = txtName.Text;

    using (container.BeginLifetimeScope()) 
    {
        var memberService = container.GetInstance<IMemberService>();
        memberService.Save(member);
    }
}

is basically what you need.

But we have now introduced a new problem. We are now using the Service Locator anti pattern to get a scoped instance of the IMemberService implementation. Therefore we need some infrastructural object which will handle this for us as a cross cutting concern in the application. A decorator is a perfect way to implement this. See also here. This will look like:

public class LifetimeScopedMemberServiceDecorator : IMemberService
{
    private readonly Func<IMemberService> decorateeFactory;
    private readonly Container container;

    public LifetimeScopedMemberServiceDecorator(Func<IMemberService> decorateeFactory,
        Container container)
    {
        this.decorateeFactory = decorateeFactory;
        this.container = container;
    }

    public void Save(List<Member> members)
    {
        using (this.container.BeginLifetimeScope())
        {
            IMemberService service = this.decorateeFactory.Invoke();

            service.Save(members);
        }
    }
}

You now register this as a (singleton) decorator in the Simple Injector Container like this:

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

The container will provide a class which depends on IMemberService with this LifeTimeScopedMemberService. In this the container will inject a Func<IMemberService> which, when invoked, will return an instance from the container using the configured lifestyle.

Adding this decorator (and its registration) and changing the lifestyles will fix the issue from your example.

I expect however that your application will in the end have a IMemberService, IUserService, ICustomerService, etc... So you need a decorator for each and every IXXXService, not very DRY if you ask me. If all services will implement Save(List<T> items) you could consider creating an open generic interface:

public interface IService<T>
{
    void Save(List<T> items); 
}

public class MemberService : IService<Member>
{
     // same code as before
}

You register all implementations in one line using Batch Registration:

container.RegisterManyForOpenGeneric(typeof(IService<>),
    new LifetimeScopeLifestyle(),
    Assembly.GetExecutingAssembly());

And you can wrap all these instances into a single open generic implementation of the above mentioned LifetimeScopeServiceDecorator.

It would IMO even be better to use the command / handler pattern (you should really read the link!) for this type of work. In very short: In this pattern every use case is translated to a message object (a command) which is handled by a single command handler, which can be decorated by e.g. a SaveChangesCommandHandlerDecorator and a LifetimeScopeCommandHandlerDecorator and LoggingDecorator and so on.

Your example would then look like:

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

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

Register as:

// Simple Injector v3.x
container.Register(typeof(IGenericRepository<>), 
    typeof(GenericRepository<>));
container.Register(typeof(ICommandHandler<>), 
    new[] { Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(ICommandHandler<>), 
    typeof(SaveChangesCommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(LifetimeScopedCommandHandlerDecorator<>),
    Lifestyle.Singleton);

// Simple Injector v2.x
container.RegisterOpenGeneric(typeof(IGenericRepository<>), 
    typeof(GenericRepository<>));
container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), 
    Assembly.GetExecutingAssembly());
container.RegisterDecorator(typeof(ICommandHandler<>), 
    typeof(SaveChangesCommandHandlerDecorator<>));
container.RegisterDecorator(typeof(ICommandHandler<>),
    typeof(LifetimeScopedCommandHandlerDecorator<>),
    Lifestyle.Singleton);

This design will remove the need for UnitOfWork and a (specific) service completely.

这篇关于使用简单的注射器与工作单位Windows窗体中的存储库模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆