leojnxs
leojnxs

Reputation: 127

How to implement IDisposable inheritance in a repository?

I am creating a generic repository and do not know what the correct way to implement the dispose functionality:

I'm not using IoC/DI, but i'll refactor my code in the future to do this, so:

My code:

IUnitOfWork Interface:

namespace MyApplication.Data.Interfaces
{
    public interface IUnitOfWork
   {
       void Save();
   }
}

DatabaseContext class:

namespace MyApplication.Data.Infra
{
    public class DatabaseContext : DbContext, IUnitOfWork
    {
        public DatabaseContext(): base("SQLDatabaseConnectionString")
        {
            Database.SetInitializer<DatabaseContext>(null);
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Same code.
            base.OnModelCreating(modelBuilder);
        }

        #region Entities mapping
        public DbSet<User> User { get; set; }
        // >>> A lot of tables
        #endregion

        public void Save()
        {
            base.SaveChanges();
        }
    }
}

IGenericRepository interface:

namespace MyApplication.Data.Interfaces
{
    public interface IGenericRepository<T> where T : class
    {
        IQueryable<T> GetAll();

        IQueryable<T> Get(Expression<Func<T, bool>> predicate);

        T Find(params object[] keys);

        T GetFirstOrDefault(Expression<Func<T, bool>> predicate);

        bool Any(Expression<Func<T, bool>> predicate);

        void Insert(T entity);

        void Edit(T entity);

        void Delete(Expression<Func<T, bool>> predicate);
    }
}

GenericRepository class:

namespace MyApplication.Data.Repositories
{
    public class GenericRepository<T> : IDisposable, IGenericRepository<T> where T  : class
    {
        private DatabaseContext _context;
        private DbSet<T> _entity;

        public GenericRepository(IUnitOfWork unitOfWork)
        {
            if (unitOfWork == null)
                throw new ArgumentNullException("unitofwork");

            _context = unitOfWork as DatabaseContext;
            _entity = _context.Set<T>();
        }

        public IQueryable<T> GetAll()
        {
            return _entity;
        }

        public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return _entity.Where(predicate).AsQueryable();
        }

        // I delete some of the code to reduce the file size.

        #region Dispose
        public void Dispose()
        {
            // HERE IS MY FIRST DOUBT: MY METHOD ITS OK?!
            // I saw implementations with GC.Suppress... and dispose in destructor, etc.

            _context.Dispose();
        }
       #endregion
    }
}

IUserRepository interface:

namespace MyApplication.Data.Interfaces
{
    public interface IUserRepository : IGenericRepository<User> { }
}

UserRepository class:

namespace MyApplication.Data.Repositories
{
    public class UserRepository : GenericRepository<User>, IUserRepository, IDisposable
    {
        public UserRepository(IUnitOfWork unitOfWork) : base(unitOfWork) {}
    }
}

UserController controller class:

namespace MyApplication.Presentation.MVCWeb.Controllers
{
    [Authorize]
    public class UserController : Controller
    {
        private IUserRepository _userRepository;
        private IProfileRepository _profileRepository;
        private IUnitOfWork _unitOfWork;

        public UserController()
        {
            this._unitOfWork = new DatabaseContext();
            this._userRepository = new UserRepository(_unitOfWork);
            this._profileRepository = new ProfileRepository(_unitOfWork);
        }

        public ActionResult List()
        {
            return View(this._userRepository.GetAll().ToList());
        }

        public ActionResult Create()
        {
            ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");

            return View(new User());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Exclude = "Id, Status, CompanyId")] User model)
        {
            ViewBag.Profiles = new SelectList(this._profileRepository.GetAll().ToList(), "Id", "Name");

            if (ModelState.IsValid)
            {
                model.EmpresaId = 1;
                model.Status = Status.Active;

                _userRepository.Insert(model);
                _unitOfWork.Save();

                return RedirectToAction("List");
            }
            else
            {
                return View();
            }
        }
    }

So, when and how i disposal my controller and/or my repositories and context?

Upvotes: 6

Views: 10051

Answers (4)

WalterAgile
WalterAgile

Reputation: 223

Context: You asked for the correct way to implement the dispose functionality.

Solution: This is an alternative for this and any other cases. this automatic alternative is suggested by Visual Studio itself (I tested in VS2017 and VS2019) :

  1. Add the IDisposable contract next to your class name => class MyClass : IDisposable
  2. Then with cursor still on that word "IDisposable", do Alt+Enter or Ctrl+. to enable "Quick Actions"
  3. Then select "Implement interface with Dispose pattern"
  4. Finally you can complete that code-pattern for your managed and unmanaged objects following suggestions indicated in comments.

Upvotes: 0

Herman Van Der Blom
Herman Van Der Blom

Reputation: 802

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.

Upvotes: 0

Keith Payne
Keith Payne

Reputation: 3082

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.

Upvotes: 14

Fede
Fede

Reputation: 44038

Have your Interface inherit from IDisposable:

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

Class:

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

Upvotes: 4

Related Questions