【问题标题】:Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form在 Windows 窗体中使用具有工作单元和存储库模式的简单注入器
【发布时间】:2015-06-30 10:18:09
【问题描述】:

我正在尝试在我的 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; }
}

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

会员存储库

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 将使我的班级成为单身人士。我不能使用 RegisterLifeTimeScope 因为它会产生错误

“IMemberService 类型的注册委托引发了异常。IUnitOfWork 注册为“Lifetime Scope”生活方式,但在 Lifetime Scope 的上下文之外请求实例”

1) 如何在具有 UnitOfWork & Repository 模式的 Windows 窗体中使用 SimpleInjector?
2) 我是否正确地实现了这些模式?

【问题讨论】:

    标签: c# entity-framework inversion-of-control repository-pattern simple-injector


    【解决方案1】:

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

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

    附带说明:Simple Injector 的diagnostic services 能够发现此配置错误,并将显示/抛出Potential Lifestyle Mismatch warning

    所以MemberRepository 是单例的,并且在整个应用程序生命周期中都有一个相同的DbContext。但是UnitOfWork 也依赖于DbContext 将收到DbContext 的不同实例,因为DbContext 的注册是瞬态的。在您的示例中,此上下文将永远不会保存新创建的Member,因为此DbContext 没有任何新创建的Member,该成员是在不同的DbContext 中创建的。

    当您将DbContext 的注册更改为RegisterSingleton 时,它将开始工作,因为现在每个服务、类或任何依赖于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 来获取IMemberService 实现的Scoped 实例。因此,我们需要一些基础设施对象,它会在应用程序中将其作为Cross-Cutting Concern 处理。 Decorator 是实现这一点的完美方式。另见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);
            }
        }
    }
    

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

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

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

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

    但是我希望你的应用程序最终会有一个IMemberServiceIUserServiceICustomerService 等...所以你需要为每个IXXXService 一个装饰器,如果不是DRY你问我。如果所有服务都实现Save(List&lt;T&gt; 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(您真的应该阅读链接!)进行此类工作。简而言之:在这种模式中,每个use case 都被转换为一个消息对象(一个命令),该对象由一个命令处理程序处理,该处理程序可以由例如SaveChangesCommandHandlerDecoratorThreadScopedCommandHandlerDecoratorLoggingDecorator 等等。

    您的示例将如下所示:

    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&lt;T&gt;

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

    【讨论】:

    • 我还建议阅读 command/handler 模式:Simple Injector 支持通过查看您的程序集来注册开放的通用接口。这种模式唯一的缺点是你会得到很多小班。另一方面,如果您是一个有组织的人,您将自动以分层文件夹结构组织您的课程。您将不可避免地需要一些组织和基础设施,至少对于您想要维护的代码。但是,这不适用于小型应用程序,因为这些模式是多余的。
    • 您会找到一种组织处理程序的方法here
    • 谢谢你的解释,给我时间了解一下
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多