DI和生命周期管理
本文关键字:管理 周期 生命 DI | 更新日期: 2023-09-27 18:04:19
当一次性对象被注入到另一个类中时,管理其生命周期的"最佳"方法是什么?我一直遇到的例子是在一个具有长生命周期的类中使用实体框架运行数据库查询。
下面是一个例子
public class CustomerViewModel
{
private ICustomerRepository _customerRepository;
public CustomerViewModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public void AddCustomer(Customer customer)
{
_customerRepository.Customers.Add(customer);
_customerRepository.SaveChanges();
}
}
上面的代码对我来说看起来很简单,但是_customerRepository
对象存在的时间和CutomerViewModel
存在的时间一样长,这比它应该存在的时间长得多。
如果我不使用DI来编写这段代码,我会这样做:
public class CustomerViewModel
{
public void AddCustomer(Customer customer)
{
using (var customerRepository = new CustomerRepository())
{
customerRepository.Customers.Add(customer);
customerRepository.SaveChanges();
}
}
}
正确处理CustomerRepository的生命周期
当一个类要求一个Disposable对象的生存期比它本身短时,这应该如何管理?
我现在使用的方法是创建一个RepositoryCreator
对象,它知道如何初始化存储库,但这感觉不对:
public class CustomerViewModel
{
private ICustomerRepositoryCreator _customerRepositoryCreator;
public CustomerViewModel(ICustomerRepositoryCreator customerRepositoryCreator)
{
_customerRepositoryCreator= customerRepositoryCreator;
}
public void AddCustomer(Customer customer)
{
using (var customerRepository = _customerRepositoryCreator.Create())
{
customerRepository.Customers.Add(customer);
customerRepository.SaveChanges();
}
}
}
那么这样做会更好吗,它可以是通用的,但是为了这个例子,我不会这样做。
public class CustomerViewModel
{
private ICustomerRepository _customerRepository;
public CustomerViewModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
public void AddCustomer(Customer customer)
{
_customerRepository.Add(customer);
}
}
public class CustomerRepository : ICustomerRepository
{
private readonly DbContext _dbContext;
public CustomerRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public void Add(Customer customer)
{
_dbContext.Customers.Add(customer);
_dbContext.Customers.SaveChanges();
}
}
有一个代理来管理生命周期
public class CustomerRepositoryLifetimeProxy : ICustomerRepository
{
private readonly _container;
public CustomerRepositoryLifetimeProxy(Container container)
{
_container = container;
}
public void Add(Customer customer)
{
using (Container.BeginScope()) //extension method
{
ICustomerRepository cRepo = Container.Resolve<ICustomerRepository>();
cRepo.Add(customer);
} // releases the instance
}
}
如果这样更好,代理应该知道DI容器,还是应该依赖工厂?
这里的问题是,您的AddCustomer
方法在您的ViewModel做得太多。视图模型不应该负责处理业务逻辑,并且存储库使用者不应该对提交工作单元一无所知,并且不应该能够将客户添加到客户列表中。
因此,将您的ICustomerResository
重构为以下内容:
public interface ICustomerRepository
{
void Add(Customer customer);
}
在这种情况下,Add
方法应该是原子的,并且自己执行提交。通过这种方式,视图模型可以依赖于该接口,并且在视图模型比客户存储库寿命更长的情况下,您可以用代理包装实际存储库:
public class CustomerRepositoryProxy : ICustomerRepository
{
private readonly Func<ICustomerRepository> repositoryFactory;
public CustomerRepositoryProxy(Func<ICustomerRepository> repositoryFactory) {
this.repositoryFactory = repositoryFactory;
}
public void Add(Customer customer) {
var repository = this.repositoryFactory.Invoke();
repository.Add(customer);
}
}
当然,如果你有几十个这样的IXXXRepository
接口,这将开始变得相当麻烦。在这种情况下,您可能希望迁移到一个泛型接口:
public interface IRepository<TEntity>
{
void Add(TEntity entity);
}
这样,您可以为所有存储库提供一个代理:
public class RepositoryProxy<TEntity> : IRepository<TEntity>
{
private readonly Func<IRepository<TEntity>> repositoryFactory;
public CustomerRepositoryProxy(Func<IRepository<TEntity>> repositoryFactory) {
this.repositoryFactory = repositoryFactory;
}
public void Add(TEntity entity) {
var repository = this.repositoryFactory.Invoke();
repository.Add(entity);
}
}
在这种情况下(假设您手工连接对象图),您可以按照如下方式构建视图模型:
new CustomerViewModel(
new RepositoryProxy<Customer>(
() => new CustomerRepository(unitOfWork)));
你甚至可以更进一步实现命令/处理程序模式和查询/处理程序模式。在这种情况下,您不会将IRepository<Customer>
注入到视图模型中,而是将ICommandHandler<AddCustomer>
注入到视图模型中,而不是将AddCustomerCommandHandler
实现注入到视图模型中,而是注入一个代理,该代理在需要时创建真正的处理程序:
public class LifetimeScopedCommandHandlerProxy<TCommand> : ICommandHandler<TCommand>
{
private readonly Func<ICommandHandler<TCommand>> decorateeFactory;
public LifetimeScopedCommandHandlerProxy(
Func<ICommandHandler<TCommand>> decorateeFactory) {
this.decorateeFactory = decorateeFactory;
}
public void Handle(TCommand command) {
var decoratee = this.decorateeFactory.Invoke();
decoratee.Handle(command);
}
}
视图模型如下所示:
public class CustomerViewModel
{
private ICommandHandler<AddCustomer> addCustomerCommandHandler;
public CustomerViewModel(ICommandHandler<AddCustomer> addCustomerCommandHandler) {
this.addCustomerCommandHandler = addCustomerCommandHandler;
}
public void AddCustomer(Customer customer)
{
this.addCustomerCommandHandler.Handle(new AddCustomer(customer));
}
}
对象图看起来和之前一样:
new CustomerViewModel(
new LifetimeScopedCommandHandlerProxy<AddCustomer>(
() => new AddCustomerCommandHandler(unitOfWork)));
当然,当使用容器时,构建这些对象图会容易得多。
如果你使用DI容器,并且你没有运行像web请求这样的东西,你将不得不显式地启动一个新的'scope'或'request'来通知容器要做什么。使用Simple Injector,你的代理将是这样的:
public class LifetimeScopedCommandHandlerProxy<TCommand> : ICommandHandler<TCommand>
{
private readonly Container container;
private readonly Func<ICommandHandler<TCommand>> decorateeFactory;
// Here we inject the container as well.
public LifetimeScopedCommandHandlerProxy(Container container,
Func<ICommandHandler<TCommand>> decorateeFactory)
{
this.container = container;
this.decorateeFactory = decorateeFactory;
}
public void Handle(TCommand command) {
// Here we begin a new 'lifetime scope' before calling invoke.
using (container.BeginLifetimeScope())
{
var decoratee = this.decorateeFactory.Invoke();
decoratee.Handle(command);
}
// When the lifetime scope is disposed (which is what the using
// statement does) the container will make sure that any scope
// instances are disposed.
}
}
在这种情况下,您的配置可能如下所示:
// This instance lives as long as its scope and will be disposed by the container.
container.RegisterLifetimeScope<IUnitOfWork, DisposableUnitOfWork>();
// Register the command handler
container.Register<ICommandHandler<AddCustomer>, AddCustomerCommandHandler>();
// register the proxy that adds the scoping behavior
container.RegisterSingleDecorator(
typeof(ICommandHandler<>),
typeof(LifetimeScopedCommandHandlerProxy<>));
container.Register<CustomerViewModel>();
一般来说,一旦一次性对象的使用结束,就由创建者来处理它。如果您注入的对象可以在整个应用程序生命周期中存在,即不需要在此期间处理它,那么正常的DI方法(您的第一个代码块)是一个很好的方法。但是,如果您需要尽快处置对象,那么工厂方法(最后一个代码块)会更有意义。