C# 将服务注入工厂是一个好的设计吗?

C# 将服务注入工厂是一个好的设计吗?,c#,wpf,entity-framework,dependency-injection,C#,Wpf,Entity Framework,Dependency Injection,我一直在阅读MarkSeemann关于DI的优秀著作,并希望在我的下一个WPF项目中实现它。但是,我有一个关于对象生存期的查询。到目前为止,大多数示例似乎都解释了MVC应用程序每个请求的存储库模式。在WPF中,真的没有替代品(我认为)。由于整个应用程序的对象图是在合成根中构建的,我如何确保我的工作单元工作正常。例如: public class ContextFactory : IContextFactory { DBContext context; public Context

我一直在阅读MarkSeemann关于DI的优秀著作,并希望在我的下一个WPF项目中实现它。但是,我有一个关于对象生存期的查询。到目前为止,大多数示例似乎都解释了MVC应用程序每个请求的存储库模式。在WPF中,真的没有替代品(我认为)。由于整个应用程序的对象图是在合成根中构建的,我如何确保我的工作单元工作正常。例如:

public class ContextFactory : IContextFactory
{
    DBContext context;

    public ContextFactory()
    {
        context = new MyDBContext();
    }

    public DBContext GetContext()
    {
        return context;
    }
}

public class ItemOneRepository() : IItemOneRepository
{
    DBContext context;

    public ItemOneRepository(IContextFactory contextFactory)
    {
        this.context = contextFactory.GetContext();
    }

    public IEnumerable GetItems()
    {
        return context.ItemOnes;
    }
}

public class ItemTwoRepository() : IItemTwoRepository
{
    DBContext context;

    public ItemTwoRepository(IContextFactory contextFactory)
    {
        this.context = contextFactory.GetContext();
    }

    public IEnumerable GetItemsByItemOneID(int itemOneID)
    {
        return context.ItemTwos.Where(i => i.itemOneID == itemOneID);
    }
}

public class ThingService : IThingService
{
    IItemOneRepository itemOneRepo;
    IItemTwoRepository itemTwoRepo;

    public ThingService(
        IItemOneRepository itemOneRepository,
        IItemTwoRepository itemTwoRepository)
    {
        itemOneRepo = itemOneRepository;
        itemTwoRepo = itemTwoRepository;
    }

    public IEnumerable Things GetThing()
    {
        var ItemOnes = itemOneRepo.GetItems();

        return ItemOnes.Select(i =>
            new Thing(
                i.FieldOne,
                i.FieldFour,
                itemRepoTwo.GetItemsByItemOneID(i.ID)
            )
        );
    }
}
在这种情况下,MyDBContext实例是通过组合根目录中的ContextFactory创建的。ItemOneRepository和ItemTwoRepository使用相同的工作单元(MyDBContext),但应用程序的其余部分也使用相同的工作单元,这显然是错误的。如果我将存储库更改为接受DBContext而不是ContextFactory,并添加一个ThingServiceFactory类,如:

public ThingServiceFactory : IThingServiceFactory
{
    IContextFactory contextFactory;

    public ThingServiceFactory(IContextFactory factory)
    {
        contextFactory = factory;
    }

    public IThingService Create()
    {
        MyDBContext context = contextFactory.Create();
        ItemOneRepository itemOneRepo = new ItemOneRepository(context);
        ItemOneRepository itemTwoRepo = new ItemTwoRepository(context);
        return new ThingService(itemOneRepo, itemTwoRepo);
    }
}
这更好,因为我现在可以将ThingServiceFactory传递给我的ViewModels,而不是ThingService的实例(包含DBContext)。然后,我可以在需要时创建一个工作单元,并在完成后立即处理它。然而,这真的是正确的方法吗。我真的需要为我需要的每一个工作单元建立一个工厂吗?当然有更好的方法

ItemOneRepository和ItemTwoRepository使用相同的 工作单元(MyDBContext),但应用程序的其余部分也是如此 这显然是错误的

如果您的工厂注册了一个临时生命周期,那么每次注入一个新实例时,您都会得到一个新实例,每次都是一个新的DBContext

但是,我建议采用更明确的工作执行单位:

public DBContext GetContext() //I would rename this "Create()"
{
    return new MyDBContext();
}
以及:

这为您提供了对工作单元和事务的细粒度控制


您可能还会问自己,与直接通过工厂使用上下文相比,存储库是否给您带来了什么好处。根据应用程序的复杂性,存储库可能会产生不必要的开销。

对于这个问题,只有一个很好的解决方案,那就是应用程序和应用程序设计

当您定义单个
ICommandHandler
抽象来定义业务事务时,您可以将该接口的封闭版本注入任何需要它的表单中。例如,假设您有一个“移动客户”操作:

public class MoveCustomerCommand
{
    public Guid CustomerId;
    public Address NewAddress;
}
您可以创建一个能够执行此命令的类:

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
    private readonly DBContext context;

    // Here we simply inject the DbContext, not a factory.
    public MoveCustomerCommandHandler(DbContext context)
    {
        this.context = context;
    }

    public void Handle(MoveCustomerCommand command)
    {
        // write business transaction here.
    }
}
这听起来有点复杂,但这完全允许您从客户机窗口中隐藏需要一个新的DbContext的事实,您也可以从业务层中隐藏这种复杂性;只需将
DbContext
注入处理程序即可。双方都不知道这一点基础设施的和平

当然,你还得把它接起来。如果没有DI库,您可以执行以下操作:

var handler = new ScopedCommandHandlerProxy<MoveCustomerCommand>(
    () => new MoveCustomerCommandHandler(new DbContext()),
    container);
var handler=new ScopedCommandHandlerProxy(
()=>新建MoveCustomerCommandHandler(新建DbContext()),
容器);
如何在DI库中注册它完全取决于所选的库,但使用Simple Injector,您可以按如下方式进行注册:

public class MoveCustomerWindow : Window
{
    private readonly ICommandHandler<MoveCustomerCommand> moveCustomerHandler;

    public MoveCustomerWindows(
        ICommandHandler<MoveCustomerCommand> moveCustomerHandler)
    {
        this.moveCustomerHandler = moveCustomerHandler;
    }

    public void Button1Click(object sender, EventArgs e)
    {
        // Here we call the command handler and pass in a newly created command.
        this.moveCustomerHandler.Handle(new MoveCustomerCommand
        {
            CustomerId = this.CustomerDropDown.SelectedValue,
            NewAddress = this.AddressDropDown.SelectedValue,
        });
    }
}
// using SimpleInjector.Extensions;

// Register all command handler implementation all at once.
container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    typeof(ICommandHandler<>).Assembly);

// Tell Simple Injector to wrap each ICommandHandler<T> implementation with a
// ScopedCommandHandlerProxy<T>. Simple Injector will take care of the rest and 
// will inject the Func<ICommandHandler<T>> for you. The proxy can be a
// singleton, sinceit will create the decoratee on each call to Handle.
container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(ScopedCommandHandlerProxy<>),
    Lifestyle.Singleton);
//使用SimpleInjector.Extensions;
//一次性注册所有命令处理程序实现。
container.RegisterManyForOpenGeneric(
类型(ICommandHandler),
类型(ICommandHandler).Assembly);
//告诉Simple Injector使用
//ScopedCommandHandlerProxy。简单的注入器将处理其余的和
//将为您注入Func。代理可以是
//singleton,因为它将在每次处理调用时创建装饰对象。
container.RegisterDecorator(
类型(ICommandHandler),
类型(ScopedCommand和HandlerProxy),
生活方式(单身);

这只是这种设计给您带来的众多优势之一。其他优点是,它可以更轻松地应用各种交叉关注点,如审计跟踪、日志记录、安全性、验证、重复数据消除、缓存、死锁预防或重试机制等。可能性是无穷的。

这是一个有趣的问题,可能会在这里找到答案。但是我建议您在优化帮助方面或者如果您正在寻找它的体系结构思想,可以找到更多的帮助。但是使用这种方法,每个存储库都有自己的DBContext。我失去了跨多个存储库的工作单元的可能性。在这种情况下,您可以将调用包装在TransactionScope中。另一个选项是将UOW传递到repository方法中,并使UOW的创建成为调用方的责任。再次,考虑一下你是否需要储存库。我一直和你在一起,直到“囚禁依赖”。我会继续努力,试着去想一想。我想我的主要困惑是容器类。这只是一节空课吗?提供被装饰者在其中创建/处置的范围是唯一的目的吗?decoratee在Handle()方法的末尾不会超出范围吗?@BoroDrummer:Container类通常是用于定义应用程序中抽象和实现之间所有映射的类(如果使用DI库)。在我的示例中,我使用了简单的Derummer:您不能从decorator的角度对Decoree的范围做任何说明。decoratee可能被重用,但我认为您经常会看到它在该handle方法之后超出了作用域。如果它被传递到另一个比handle()方法本身寿命更长的进程,那么decoratee的作用域肯定只能在handle()之外继续存在。SimpleInjector将如何使用BeginLifeTimeScope()来处理这个问题?。。。我想这是另一个讨论:)
var handler = new ScopedCommandHandlerProxy<MoveCustomerCommand>(
    () => new MoveCustomerCommandHandler(new DbContext()),
    container);
// using SimpleInjector.Extensions;

// Register all command handler implementation all at once.
container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    typeof(ICommandHandler<>).Assembly);

// Tell Simple Injector to wrap each ICommandHandler<T> implementation with a
// ScopedCommandHandlerProxy<T>. Simple Injector will take care of the rest and 
// will inject the Func<ICommandHandler<T>> for you. The proxy can be a
// singleton, sinceit will create the decoratee on each call to Handle.
container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(ScopedCommandHandlerProxy<>),
    Lifestyle.Singleton);