使用具有 Windows 形式的工作单元和存储库模式的简单注入器

Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form

我正在尝试在我的 windows 表单应用程序中实施 IoC。我选择了 Simple Injector,因为它速度快且重量轻。我还在我的应用程序中实现了工作单元和存储库模式。这是结构:

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

通用存储库:

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

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

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

        container.Verify();
    }
}

当我运行程序并添加一个成员时,它没有保存到数据库中。如果我将 container.Register 更改为 container.RegisterSingle,它将保存到数据库中。根据文档,RegisterSingle 将使我的 class 成为单身人士。我不能使用 RegisterLifeTimeScope,因为它会产生错误

"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 和 Repository 模式的 Windows 表单中使用 SimpleInjector?
2) 我是否正确实施了模式?

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

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

旁注: 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 中创建的。

当您将 DbContext 的注册更改为 RegisterSingleton 时,它将开始工作,因为现在每项服务、class 或任何依赖于 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 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。这看起来像:

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

您现在在简单注入器 Container 中将其注册为(单例)装饰器,如下所示:

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

容器将提供一个 class,它依赖于 IMemberService 和这个 ThreadScopedMemberServiceDecorator。在这种情况下,容器将注入一个 Func<IMemberService> ,当调用它时,将使用配置的生活方式从容器中 return 一个实例。

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

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

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.

的单个开放通用实现中

IMO 甚至更好地使用 command / handler pattern (you should really read the link!) for this type of work. In very short: In this pattern every use case 被翻译成一个消息对象(一个命令),它由一个命令处理程序处理,可以通过例如装饰。一个 SaveChangesCommandHandlerDecorator 和一个 ThreadScopedCommandHandlerDecoratorLoggingDecorator 等等。

您的示例将如下所示:

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 和(特定)服务的需求。