关于c#:使用简单的注射器和工作单元&

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

我正试图在我的Windows窗体应用程序中实现IOC。我的选择落在了简单的注射器上,因为它既快又轻。我也在我的应用程序中实现了工作单元和存储库模式。结构如下:

DbContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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>();\
    }
}

模型:

1
2
3
4
5
public class Member
{
    public int MemberID { get; set; }
    public string Name { get; set; }
}

通用存储库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
    }
}

成员存储库:

1
2
3
4
5
6
public class MemberRepository : GenericRepository<Member>, IMemberRepository
{
    public MemberRepository(DbContext context)
        : base(context)
    { }
}

UnitOfWork:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
    }
}

MemberService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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();
    }
}

在成员窗体中,我只添加一个文本框来输入成员名称,并添加一个按钮保存到数据库。这是成员形式的代码:

会员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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),如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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将使我的类成为单例。我无法使用RegisterFeTimeScope,因为它将生成错误

"为IMemberService类型注册的委托引发了异常。iUnitOfWork注册为"终身范围"生活方式,但实例是在终身范围之外请求的。"

1)如何将Windows窗体中的SimpleInjector与UnitOfWork&Repository模式一起使用?< BR>2)我是否正确地实现模式?


您的问题在于服务、存储库、UnitOfWork和DbContext之间的生活方式不同。好的。

由于MemberRepository具有单体的生活方式,因此简单的注入器将创建一个实例,在应用程序的持续时间内重复使用该实例,使用WinForms应用程序可能需要几天、甚至几周或几个月。将MemberRepository注册为singleton的直接后果是,无论注册中使用何种生活方式,此类的所有依赖项也将成为singleton。这是一个称为强制依赖的常见问题。好的。

附带说明:简单注射器的诊断服务能够发现这种配置错误,并显示/抛出潜在的生活方式不匹配警告。好的。

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

当您将DbContext的注册更改为RegisterSingleton时,它将开始工作,因为现在依赖DbContext的每个服务、类或任何其他服务都将获得相同的实例。好的。

但这肯定不是解决方案,因为在应用程序的生命周期中使用一个DbContext会给您带来麻烦,正如您可能已经知道的那样。这在本帖中有很详细的解释。好的。

您需要的解决方案是使用您已经尝试过的DbContext的作用域实例。您缺少一些关于如何使用简单注入器的生存期范围特性的信息(以及其他大多数容器)。当使用限定范围的生活方式时,必须有一个活动范围,因为异常消息清楚地说明了这一点。启动生存期范围非常简单:好的。

1
2
3
4
5
6
using (ThreadScopedLifestyle.BeginScope(container))
{
    // all instances resolved within this scope
    // with a ThreadScopedLifestyleLifestyle
    // will be the same instance
}

你可以在这里详细阅读。好的。

将注册更改为:好的。

1
2
3
4
5
6
7
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()改为:好的。

1
2
3
4
5
6
7
8
9
10
11
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);
    }
}

基本上就是你需要的。好的。

但我们现在引入了一个新问题。我们现在使用服务定位器反模式来获取IMemberService实现的作用域实例。因此,我们需要一些基础设施对象来处理这个问题,作为应用中的一个交叉关注点。装饰器是实现这一点的完美方法。这里也可以看到。这看起来像:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中将此注册为(单例)装饰器,如下所示:好的。

1
2
3
4
container.RegisterDecorator(
    typeof(IMemberService),
    typeof(ThreadScopedMemberServiceDecorator),
    Lifestyle.Singleton);

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

添加这个装饰器(及其注册)和改变生活方式将从您的示例中解决这个问题。好的。

不过,我希望你的申请最终会有一个IMemberServiceIUserServiceICustomerService等。所以你需要为每一个IXXXService都配备一个装饰师,如果你问我的话,不会很干。如果所有服务都将实现Save(List items),您可以考虑创建一个开放的通用接口:好的。

1
2
3
4
5
6
7
8
9
public interface IService<T>
{
    void Save(List<T> items);
}

public class MemberService : IService<Member>
{
     // same code as before
}

您可以使用批注册在一行中注册所有实现:好的。

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

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

使用命令/处理程序模式会更好(您应该真正阅读链接!)对于这类工作。简而言之:在这个模式中,每一个用例都被转换成一个消息对象(一个命令),它由一个命令处理程序来处理,它可以由一个SaveChangesCommandHandlerDecorator和一个ThreadScopedCommandHandlerDecoratorLoggingDecorator等修饰。好的。

您的示例如下所示:好的。

1
2
3
4
5
6
7
8
9
public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

public class CreateMemberCommand
{
    public string MemberName { get; set; }
}

使用以下处理程序:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 });
    }
}

这些都可以注册如下:好的。

1
2
3
4
5
6
7
8
9
10
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和(特定)服务的需求。好的。好啊。