c# - 在 Windows 窗体中使用带有工作单元和存储库模式的简单注入(inject)器

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

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

数据库上下文 :

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

成员(member)资料库 :
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);
    }
}

成员(member)服务 :
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();
    }
}

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

frm成员(member) :
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)程序.cs 如下面的代码所示:
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.Registercontainer.RegisterSingle ,它将保存到数据库。从文档中,RegisterSingle将使我的类(class)成为单例。我不能使用 RegisterLifeTimeScope 因为它会产生一个错误

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

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

最佳答案

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

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

作为旁注:diagnostic services Simple Injector 能够发现这个配置错误,并会显示/抛出 Potential Lifestyle Mismatch warning .

所以MemberRepository是 Singleton 并且有一个相同的 DbContext在整个应用程序生命周期中。但是UnitOfWork ,它也依赖于 DbContext将收到 DbContext 的不同实例,因为注册为DbContext是 transient 。在您的示例中,此上下文永远不会保存新创建的 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 的作用域实例实现。因此,我们需要一些基础设施对象来作为 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 .在这个容器中将注入(inject)一个 Func<IMemberService>当调用时,它将使用配置的生活方式从容器返回一个实例。

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

但是,我希望您的应用程序最终会有一个 IMemberService , IUserService , ICustomerService , 等等... 所以你需要为每一个 IXXXService 一个装饰器,不太DRY如果你问我。如果所有服务都将实现 Save(List<T> items)您可以考虑创建一个开放的通用接口(interface):
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 的单个开放通用实现中。 .

如果使用 command / handler pattern,IMO 会更好(你真的应该阅读链接!)对于这种类型的工作。简而言之:在这种模式中,每个 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和一个(特定的)服务。

关于c# - 在 Windows 窗体中使用带有工作单元和存储库模式的简单注入(inject)器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29802957/

相关文章:

mysql - 使用 Entity Framework 将 MySQL 和 MSSQL 用于两个不同的数据库

spring - JavaEE 有控制反转吗?

c# - 使用 IoC 容器的合适情况?

c# - .NET Core 中的 Crossgen 编译

c# - 使用 DataRowExtension 解析 TimeSpan

c# - Directshow 和 .Net - 位图显示图像左侧的右侧条纹?

wpf - 无法在 ComboBox WPF 中使用多选项目保存

c# - EntityFramework - 实体代理错误

c# - 在 Autofac 的 lambda 注册期间访问上下文信息?

c# - 从选定组中获取随机枚举