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

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

问题描述

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

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:

数据库上下文:

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

工作单位:

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

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

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 将使我的班级成为单身人士.我不能使用 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 注册为‘Lifetime Scope’生活方式,但在 Lifetime Scope 的上下文之外请求了该实例"

"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) 如何在带有 UnitOfWork 的 Windows 窗体中使用 SimpleInjector &存储库模式?
2) 我是否正确地实现了模式?

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

推荐答案

您遇到的问题是您的服务、存储库、工作单元和 dbcontext 之间的生活方式不同.

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

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

Because the MemberRepository has a Singleton lifestyle, Simple Injector will create one instance which will be reused for the duration 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 Singletons as well, no matter what lifestyle is used in the registration. This is a common problem called Captive Dependency.

附注:诊断服务 Simple Injector 能够发现此配置错误,并会显示/抛出 潜在生活方式不匹配警告.

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 是 Singleton 并且在整个应用程序生命周期中具有相同的 DbContext.但是 UnitOfWork 也依赖于 DbContext 将收到 DbContext 的不同实例,因为 DbContext 是瞬态的.在您的示例中,此上下文永远不会保存新创建的 Member,因为此 DbContext 没有任何新创建的 Member,该成员已创建在不同的 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 的注册更改为 RegisterSingleton 时,它将开始工作,因为现在每个服务、类或任何依赖于 DbContext 的都将得到相同的实例.

When you change the registration of DbContext to RegisterSingleton 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 的 Scoped 实例.您缺少一些有关如何使用 Simple Injector(以及大多数其他容器)的生命周期范围功能的信息.使用 Scoped 生活方式时,必须有一个活动范围,因为异常消息明确指出.启动生命周期作用域非常简单:

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 lifestyle there must be an active scope as the exception message clearly states. Starting a lifetime scope is pretty simple:

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<IMemberRepository, MemberRepository>(Lifestyle.Scoped);
container.Register<IMemberService, MemberService>(Lifestyle.Scoped);
container.Register<DbContext, MemberContext>(Lifestyle.Scoped);
container.Register<IUnitOfWork, UnitOfWork>(Lifestyle.Scoped);

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

and changing the code from btnSaveClick() to:

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

基本上就是你所需要的.

is basically what you need.

但是我们现在引入了一个新问题.我们现在使用 Service Locator anti pattern 来获取 Scoped 实例IMemberService 实现.因此,我们需要一些基础设施对象来为我们处理这个问题作为 Cross-Cutting Concern应用程序.Decorator 是实现这一点的完美方式.另请参阅此处.这看起来像:

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 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 中的(单例)装饰器,如下所示:

You now register this as a (Singleton) Decorator in the Simple Injector Container like this:

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

容器将提供一个依赖于 IMemberService 和这个 ThreadScopedMemberServiceDecorator 的类.在这种情况下,容器将注入一个 Func,当调用时,它将使用配置的生活方式从容器返回一个实例.

The container will provide a class which depends on IMemberService with this ThreadScopedMemberServiceDecorator. 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.

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

I expect however that your application will in the end have an 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
}

您在一行中使用批量注册:

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

并且您可以将所有这些实例包装到上述 ThreadScopedServiceDecorator 的单个开放通用实现中.

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

如果使用 命令/处理程序,IMO 会更好模式(您应该真正阅读链接!)用于此类工作.简而言之:在这种模式中,每个用例都被转换为一个消息对象(一个命令)由单个命令处理程序处理,可以通过例如装饰一个 SaveChangesCommandHandlerDecorator 和一个 ThreadScopedCommandHandlerDecoratorLoggingDecorator 等等.

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

并且表单现在可以依赖于ICommandHandler:

And the form can now depend on 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 和(特定)服务的需求.

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

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

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