什么是最好的'在将一次性物体注入另一个类时管理其生命周期的方法。我一直在运行的示例是在具有较长生命周期的类中使用实体框架运行数据库查询。
这是一个例子
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容器,还是应该依赖工厂?
答案 0 :(得分:2)
这里的问题是ViewModel中的AddCustomer
方法做了很多。 viewmodel不应该负责处理业务逻辑,并且存储库使用者不应该对提交工作单元一无所知,也不应该将客户添加到客户列表中。
相反,请将您的ICustomerResository
重构为以下内容:
public interface ICustomerRepository
{
void Add(Customer customer);
}
在这种情况下,Add
方法应该是原子的并且自己进行提交。这样viewmodel可以依赖于该接口,并且如果viewmodel超过客户存储库,您可以使用代理包装真实存储库:
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)));
您甚至可以更进一步实施command/handler pattern和query/handler pattern。在这种情况下,您不会将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请求那样运行,则必须明确地启动新的“范围”或“请求”以通知容器要执行的操作。使用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>();
答案 1 :(得分:0)
一般情况下,创建者应尽快处置一次性物品。如果您注入的对象可以在整个应用程序生命周期中存活,即不需要在此期间处理它,那么正常的DI方法(您的第一个代码块)是一个很好的方法。但是,如果您需要尽快处理对象,那么工厂方法更有意义(最后一个代码块)。