Nick Williams
Nick Williams

Reputation: 1267

DI and Lifetime Management

what is the 'best' way to manage the lifecycle of a disposable object when it is injected into another class. The example I keep running into is when running database queries using entity framework in a class that has a long lifetime.

Here is an example

public class CustomerViewModel
{
  private ICustomerRepository _customerRepository;

  public CustomerViewModel(ICustomerRepository customerRepository)
  {
    _customerRepository = customerRepository;
  }

  public void AddCustomer(Customer customer)
  {
     _customerRepository.Customers.Add(customer);
     _customerRepository.SaveChanges();
  }
}

The code above looks perfectly innocent to me, however the _customerRepository object exists for as long as the CutomerViewModel exists for, which is much longer than it should.

If I were writting this code without DI i would do this:

public class CustomerViewModel
{
  public void AddCustomer(Customer customer)
  {
     using (var customerRepository = new CustomerRepository())
     {
       customerRepository.Customers.Add(customer);
       customerRepository.SaveChanges();
     }
  }
}

Which handles the lifecycle of CustomerRepository correctly.

How is this supposed to be managed when a class requires a Disposable object to have a shorter lifetime than itself?

The method I am using now is to create a RepositoryCreator object, which knows how to initialize a repository, but this feels wrong:

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

UPDATE

So would doing something like this be preferable, it could be made generic but for the sake of this example I will not do this.

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

And have a proxy which manages lifetime

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

If this is better, should the Proxy know about the DI container, or should it rely on a factory?

Upvotes: 1

Views: 786

Answers (2)

Steven
Steven

Reputation: 172606

The problem here is that your AddCustomer method in your ViewModel does to much. The viewmodel should not be responsible of handling business logic, and the repositories consumer shouldn't know nothing about committing a unit of work and should not be able to add a customer to the list of customers.

So instead, refactor your ICustomerResository to the following:

public interface ICustomerRepository
{
    void Add(Customer customer);
}

In this case, the Add method should be atomic and do the commit itself. This way the viewmodel can depend on that interface and in case the viewmodel outlives the customer repository, you can wrap the real repository with a proxy:

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

Of course this will start to become quite cumbersome if you have dozens of those IXXXRepository interfaces. In that case, you might want to migrate to one generic interface instead:

public interface IRepository<TEntity>
{
    void Add(TEntity entity);
}

This way you can have one single proxy for all repositories:

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

In that case (assuming you wire your object graphs by hand) you can build up the viewmodel as follows:

new CustomerViewModel(
    new RepositoryProxy<Customer>(
        () => new CustomerRepository(unitOfWork)));

You can even take it one step further and implement the command/handler pattern and query/handler pattern. In that case you don't inject a IRepository<Customer> into your view model, but you inject an ICommandHandler<AddCustomer> into the view model and instead of injecting the AddCustomerCommandHandler implementation into the view model, you inject a proxy that creates the real handler when needed:

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

The view model will look as follows:

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

And the object graph will look similar as before:

new CustomerViewModel(
    new LifetimeScopedCommandHandlerProxy<AddCustomer>(
        () => new AddCustomerCommandHandler(unitOfWork)));

Of course, it will be much easier building these object graphs when using a container.

UPDATE

If you use a DI container, and you're not running in something like a web request, you will have to start a new 'scope' or 'request' explictly to inform the container what to do. With Simple Injector your proxy will looks like this:

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.
    }
}

In that case your configuration might look like this:

// 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>();

Upvotes: 2

Edin
Edin

Reputation: 1496

In general it is up to the creator to dispose a disposable object as soon es it is done with its usage. If your injected object can live through entire application lifecycle, i.e. without needing to dispose it in the meantime, than the normal DI approach (your first code block) is a good way to go. However, if you need to dispose the object as soon as possible, than a factory approach makes much more sense (last code block).

Upvotes: 0

Related Questions