How to implement IDisposable inheritance in a repository?

三世轮回 提交于 2019-12-03 01:35:27
Keith Payne

UPDATED ANSWER:

Example of my controller:

private IGenericRepository<Profile> _repository; 
private IUnitOfWork _unitOfWork = new DatabaseContext();
public ProfileController() 
{ 
    this._repository = new GenericRepository<Profile>(_unitOfWork); 
}

public class ProfileController : Controller 
{
    private IGenericRepository<Profile> _repository;
    private IUnitOfWork _unitOfWork = new DatabaseContext();  
    public ProfileController()  
    { 
        this._repository = new GenericRepository<Profile>(_unitOfWork);  
    }
}

With the code that you have now, the best thing to do is to override Controller.Dispose(bool disposing) and dispose of the repository in there.

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        IDisposable d = _repository as IDisposable;
        if (d != null)
            d.Dispose();
        GC.SupressFinalize(this);
    }
    base.Dispose(disposing);
}

Once you start using an IOC container, all of this disposal code will go away. Construction and disposal should happen at the container level. The container will be the only place where it is known or cared that the repository and unit of work are disposable.

But I suspect that none of these classes need to be disposable in the first place. You should use SqlConnection in a using block. It does not need to be a class-level field in DatabaseContext.


Please forgive the length of this answer. I must establish some fundamentals in order for my recommendation to make sense.

S.O.L.I.D.

SOLID ... stands for five basic principles of object-oriented programming and design. The two principles that are of concern here are the I and S.

Interface Segregation Principle (ISP)

Including IDisposable on the IGenericRepository<T> interface explicitly violates the ISP.

It does this because a repository's disposability (and the imperative to properly dispose of the object) is unrelated to it's designed purpose, which is to Get and Store aggregate root objects. By combining the interfaces together, you get a non-segregated interface.

Why is this important aside from violating some theoretical principle? I will explain that below. But first I have to cover another of the SOLID principles:

Single Resposibility Principle

I always keep this article, Taking the Single Responsibility Principle Seriously, handy when I re-factor functional code into good OO code. This is not an easy subject and the article is very dense. But it is invaluable as a thorough explanation of SRP.

Understanding SRP and disregarding the flaws in 99.9% of all MVC controllers out there which take many DI constructor parameters, the one flaw that is pertinent here is:

Making the controller be responsible for both using the repository and disposing of the repository crosses over to a different level of abstraction, and this violates the SRP.

Explanation:

So you're calling at least two public methods on the repository object. One to Get an object and one to Dispose of the repository. There's nothing wrong with that, right? Ordinarily no, there is nothing wrong with calling two methods on the repository or any object.

But Dispose() is special. The convention of disposal of an object is that it will no longer be useful after being disposed. This convention is one reason why the using pattern establishes a separate code block:

using (var foo = new Bar())
{
    ...  // This is the code block
}

foo.DoSomething();  // <- Outside the block, this does not compile

This is technically legal:

var foo = new Bar();
using (foo)
{
    ...  // This is the code block
}

foo.DoSomething();   // <- Outside the block, this will compile

But this gives a warning of usage of an object after it has been disposed. This is not proper which is why you don't see examples of this usage in the MS documentation.

Because of this unique convention, Dispose(), is more closely related to constructing and destructing an object than to the usage of other members of an object, even though it is exposed as a simple public method.

Construction and Disposal are on the same low level of abstraction. But because the controller is not constructing the repository itself, it lives on a higher level of abstraction. When disposing the repository, it is reaching outside of its level of abstraction to fiddle with the repository object at a different level. This violates the SRP.

Code Reality

Okay, all that theory means exactly what as far as my code is concerned?

Consider what the controller code looks like when it disposes of the repository itself:

public class CustomerController : Controller
{
    IGenericRepository<Customer> _customerRepo;
    IMapper<Customer, CustomerViewModel> _mapper;

    public CustomerController(
        IMapper<Customer, CustomerViewModel> customerRepository,
        IMapper<Customer, CustomerViewModel> customerMapper)
    {
        _customerRepo = customerRepository;
        _customerMapper = customerMapper;
    }

    public ActionResult Get(int id)
    {
        CustomerViewModel vm;
        using (_customerRepo)  // <- This looks fishy
        {
            Customer cust = _customerRepo.Get(id);
            vm = _customerMapper.MapToViewModel(cust);
        }
        return View(wm);
    }

    public ActionResult Update(CustomerViewModel vm)
    {
        Customer cust = _customerMapper.MapToModel(vm);
        CustomerViewModel updatedVm;
        using(_customerRepo)  // <- Smells like 3 week old flounder, actually
        {
            Customer updatedCustomer = _customerRepo.Store(cust);
            updatedVm = _customerMapper.MapToViewModel(updatedCustomer);
        }
        return View(updatedVm);
    }
}

The controller must receive a useful (non disposed) repository when it is constructed. This is a common expectation. But don't call two methods in the controller or it will break. This controller is a one-shot deal only. Plus, you cannot even call one public method from within another. E.g. the Update method could call Get after storing the model in the repository in order to return an updated Customer View. But this would blow up.

Conclusion

Receiving the repository as a parameter means that something else is responsible for creating the repository. That something else should also be responsible for properly disposing of the repository.

The alternative of disposing an object at the same level of abstraction as using its (other) public members, when the lifetime of the object and possible subsequent usage of the object is not under direct control, is a ticking time bomb.

The rule of IDisposable is this: It is never acceptable to inherit IDisposable in a another functional interface declaration because IDisposable is never a functional concern, but an implementation detail only.

Have your Interface inherit from IDisposable:

public interface IGenericRepository<T> : IDisposable where T : class
{
   //...
}

Class:

public class GenericRepository<T> : IGenericRepository<T>
{
    public void Dispose()
    {
        //....
    }
}

Single Resposibility Principle

  • If you look at SRP and use that in MVC it tells you that when you have transactions that are made up of multiple methods (CRUDS) thats not a correct way of implementing the transactions.

  • So you should put those multiple (CRUD) methods in one method and in that Transaction method implement using for the database context so you can pass that database context to those called private methods that make up the transaction method. If all those (CRUD) methods succeed you can call the Commit method (SaveChanges). When those methods fail you do nothing (=Rollback). So make sure no Commit is executed in those methods but delegate it to the Transaction method.

  • By implementing the 2nd bullet IDispose is not needed in Repositories because using takes care of cleaning up the database context.

  • If Connection Pooling is used thats an efficient way of implementing it and it will be scalable.

In the way you setup a Class above it feels wrong. Because in the constructor for the Repository you could set the database context and use that same contextin several methods but then you would need to implement IDispose because you need to cleanup the connections yourself. In lots of examples for Repositories its implemented this way buts from a SRP point of view thats not right and should be changed according to the 2nd bullet above.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!