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

Wil*_*lly 10 c# entity-framework inversion-of-control repository-pattern simple-injector

我正在尝试在我的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>();\
    }
}
Run Code Online (Sandbox Code Playgroud)

型号:

public class Member
{
    public int MemberID { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

MemberRepository:

public class MemberRepository : GenericRepository<Member>, IMemberRepository
{
    public MemberRepository(DbContext context)
        : base(context)
    { }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

会员服务:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行程序并添加一个成员时,它不会保存到数据库.如果我换container.Registercontainer.RegisterSingle,它会保存到数据库中.从文档中,RegisterSingle将使我的班级成为单身人士.我无法使用RegisterLifeTimeScope,因为它会产生错误

"IMemberService类型的注册委托引发了异常.IUnitOfWork注册为'Lifetime Scope'生活方式,但实例是在终身范围的上下文之外请求的"

1)如何在Windows窗体中使用带有UnitOfWork和Repository模式的SimpleInjector?
2)我是否正确实施了模式?

Ric*_*Net 19

您遇到的问题是您的服务,存储库,unitofwork和dbcontext之间的生活方式存在差异.

因为MemberRepository具有Singleton生活方式,Simple Injector将创建一个将在应用程序持续时间内重用的实例,使用WinForms应用程序可能需要几天甚至几周或几个月.注册MemberRepository单身人士的直接后果是,无论在注册中使用什么样的生活方式,这个类的所有依赖关系都将成为单身人士.这是一个称为Captive Dependency的常见问题.

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

因此,MemberRepositorySingleton在DbContext整个应用程序生命周期中都是一样的.但是UnitOfWork,具有依赖性的,DbContext也会收到不同的实例DbContext,因为注册DbContext是Transient.在您的示例中,此上下文将永远不会保存新创建的,Member因为DbContext它没有任何新创建Member的成员,该成员是以不同的方式创建的DbContext.

当您更改登记DbContextRegisterSingleton它会开始工作,因为现在每一个服务类或任何因DbContext会得到相同的实例.

但这肯定不是解决方案,因为DbContext在应用程序的生命周期中使用一个会让你陷入麻烦,正如你可能已经知道的那样.这是很详细地解释这个职位.

您需要的解决方案是使用DbContext已经尝试过的Scoped实例.您缺少一些有关如何使用Simple Injector(以及其他大多数容器)的生命周期范围功能的信息.当使用Scoped生活方式时,必须有一个活动范围,因为异常消息明确指出.启动生命周期范围非常简单:

using (ThreadScopedLifestyle.BeginScope(container)) 
{
    // all instances resolved within this scope
    // with a ThreadScopedLifestyleLifestyle
    // will be the same instance
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里详细阅读.

将注册更改为:

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);
Run Code Online (Sandbox Code Playgroud)

并将代码更改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);
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上是你需要的.

我们现在引入了一个新问题.我们现在使用Service Locator反模式来获取实现的Scoped实例IMemberService.因此,我们需要一些基础设施对象,它将为我们处理这个问题,作为应用程序中的交叉关注点.一个装饰是实现这种完美的方式.另见这里.这看起来像:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您现在将此注册为Simple Injector中的(Singleton)装饰器,Container如下所示:

container.RegisterDecorator(
    typeof(IMemberService), 
    typeof(ThreadScopedMemberServiceDecorator),
    Lifestyle.Singleton);
Run Code Online (Sandbox Code Playgroud)

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

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

但是我希望您的应用程序将在年底有IMemberService,IUserService,ICustomerService,等...所以,你需要为每一个装饰IXXXService,不是很,如果你问我.如果所有服务都将实现,Save(List<T> items)您可以考虑创建一个开放的通用接口:

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

public class MemberService : IService<Member>
{
     // same code as before
}
Run Code Online (Sandbox Code Playgroud)

您使用批量注册在一行中注册所有实现:

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

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

对于这种类型的工作,IMO甚至可以更好地使用命令/处理程序模式(您应该真正阅读链接!).非常简短:在这种模式中,每个用例都被转换为一个消息对象(一个命令),它由一个命令处理程序处理,可以通过例如a SaveChangesCommandHandlerDecorator和a ThreadScopedCommandHandlerDecorator等进行修饰LoggingDecorator.

您的示例将如下所示:

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

public class CreateMemberCommand
{
    public string MemberName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用以下处理程序:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

表单现在可以依赖于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 });
    }
}
Run Code Online (Sandbox Code Playgroud)

这些都可以注册如下:

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);
Run Code Online (Sandbox Code Playgroud)

此设计将完全消除对UnitOfWork(特定)服务的需求.

  • 您将找到一种组织处理程序的方法[这里](http://qujck.com/managing-command-and-query-parameter-objects/) (2认同)