Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement IDisposable inheritance in a repository?

Tags:

c#

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?

like image 700
leojnxs Avatar asked Dec 03 '13 18:12

leojnxs


1 Answers

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.

like image 185
Keith Payne Avatar answered Sep 28 '22 19:09

Keith Payne