使用带有组的工作与简单的放大器喷油器;在Windows窗体库模式 [英] Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form

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

问题描述

我想在我的Windows窗体应用程序来实现的IoC。我的选择落在了简单的喷油器,因为它的快速,重量轻。我在我的应用程序也实现工作和存储库模式的单位。这里是结构:

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

型号

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

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

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

我实现SimpleInjector(参见 http://simpleinjector.readthedocs.org/en /latest/windowsformsintegration.html )在的Program.cs 如以下的code:

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

当我运行该程序并添加一个成员,它不保存到数据库中。如果我改变了 container.Register container.RegisterSingle ,它会保存到数据库中。从文档, RegisterSingle 将我的课是一个Singleton。我无法通过RegisterLifeTimeScope,因为它会产生一个错误

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

注册委托类型IMemberService引发异常。该IUnitOfWork注册为终身范围的生活方式,但实例请求一个终身范围的情况下外

"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)如何在Windows窗体使用SimpleInjector用的UnitOfWork&安培;存储库模式?

2)我正确执行模式?

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

推荐答案

您的问题是,在你的服务,储存库,的UnitOfWork并的DbContext之间生活方式的差异。

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

由于该 MemberRepository 有一个单身一辈子简单的喷油器将创建一个实例,它将应用程序的生命周期中重复使用,这可能是数天,甚至数周或数月与一个WinForms应用程序。从注册 MemberRepository 作为单身的直接后果是,该类的所有依赖将成为单为好,不管什么样的生活方式是在注册使用。这些依赖关系被称为圈养依赖

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.

所以 MemberRepository 是独立的,并有一个和整个应用程序生存期相同的DbContext。但是的UnitOfWork,这也对的DbContext的依赖将获得的DbContext的不同实例,因为的DbContext注册是短暂的。这方面将在你的榜样,永远保存新创建成员,因为这的DbContext没有任何新创建成员的部件是在一个不同的DbContext创建。

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.

当你改变的DbContext的登记 RegisterSingle 将开始工作,因为现在这取决于每一个的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.

但是,这是肯定的不可以的解决方案,因为有一个的DbContext应用程序的生命周期将会让你陷入困境,因为你可能已经知道。这是非常详细这帖子解释说。

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.

您需要使用的DbContext,你已经尝试过的作用域实例的解决方案。你缺少了如何使用简单的喷油器的寿命范围功能的一些信息(以及大多数其他容器的外面)。当使用范围的一生必须有一个有效使用期范围,因为异常消息明确指出。开始了一生范围为pretty简单:

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
}

您可以详细<读href=\"https://simpleinjector.readthedocs.org/en/latest/lifetimes.html#per-lifetime-scope\">here.

更改注册为:

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

和改变从的code 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);
    }
}

基本上是你所需要的。

is basically what you need.

但是:我们现在已经推出了新的问题。我们现在正在使用的服务定位器反面模式以获得<$ C的范围实例$ C> IMemberService 的实施。因此,我们需要一些基础设施对象,将处理这对我们作为一个横切关注点在应用程序中。一个装饰是实现这个完美的方式。参见<一个href=\"https://simpleinjector.readthedocs.org/en/latest/advanced.html?highlight=decorator#decorators\">here.这将是这样的:

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

容器将提供取决于 IMemberService LifeTimeScopedMemberService A类。在这个容器将注入 Func键&LT; IMemberService&GT; 其中,被调用时,就会从容器中使用配置的生活方式返回实例。

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.

我期待您的应用程序将在年底有一个IMemberService,IUserService,ICustomerService等......所以,你需要为每个一个装饰,每一个IXXXService,并不十分的如果你问我干。如果所有的服务都将执行保存(名单&LT; T&GT;项目)你可以考虑创建一个开放的通用接口:

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
}

您注册的所有实现在一行中使用<一个href=\"https://simpleinjector.readthedocs.org/en/latest/advanced.html#batch-automatic-registration\">Batch报名:

You register all implementations in one line using Batch Registration:

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

和你可以用所有这些实例成一个开放式泛型实施上述 LifetimeScopeServiceDecorator

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

这将IMO甚至是更好的使用命令/处理模式 (你应该阅读的链接!)这种类型的工作。在很短的:在这种模式每用例的被翻译成由一单个处理的消息对象(命令)命令处理程序,其可以通过例如进行装饰一个 SaveChangesCommandHandlerDecorator LifetimeScopeCommandHandlerDecorator LoggingDecorator 等。

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.

然后,您的例子如下:

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

注册为:

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

此设计将删除的UnitOfWork 的必要性和(具体的)服务完全。

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

这篇关于使用带有组的工作与简单的放大器喷油器;在Windows窗体库模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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