0
votes

Hello I implemented UoW/Repository pattern in my application, following inter alia MSDN tutorials. I am confused however when it comes to disposing of the context (which admittedly is because of the fact that I yet have a lot to learn about memory management in C#).

Anyway, I have a context which is passed to:

  • unit of work
  • generic repositories
  • specific repositories

My question: when exactly I should dispose of that context and which of the interfaces I have should derive from IDisposable / which classes should implement IDisposable?

Currently, I derive from IDisposable in IGenericRepository and IUnitOfWork and then implement the Dispose method in GenericRepository and UnitOfWork. But in the MSDN tutorials the implementation of the Dispose method is in the specific repositories rather then in the generic repository which is the reason for my confusion. If I am working with the same instance of context passing from base class (generic repository) to a specific repository which gets the context using base constructor, should it not be enough if I dispose it in the generic repository?

Interfaces:

public interface IUnitOfWork : IDisposable
{
    IAccountsRepository Accounts { get; }
    ITransactionsRepository Transactions { get; }
    IAccountGroupsRepository AccountGroups { get; }

    void Complete();
}

public interface IGenericRepository<TEntity> : IDisposable where TEntity : class
{
    void Add(TEntity entity);
    void Edit(TEntity entity);
    IEnumerable<TEntity> GetAll();
    TEntity GetById(object id);
    void Remove(object id);
    void Remove(TEntity entity);
}

public interface IAccountsRepository : IGenericRepository<Account>
{
    IEnumerable<Account> GetForUser(string applicationUserId);
    string GetAccountName(int accountId);
}

Implementation:

public class UnitOfWork : IUnitOfWork
{
    private readonly TinyBooksDbContext _context;
    private bool _disposed;

    public IAccountsRepository Accounts { get; }
    public ITransactionsRepository Transactions { get; }
    public IAccountGroupsRepository AccountGroups { get; set; }


    public UnitOfWork(TinyBooksDbContext context)
    {
        _context = context;
        Accounts = new AccountsRepository(_context);
        Transactions = new TransactionsRepository(_context);
        AccountGroups = new AccountGroupsRepository(_context);
    }

    public void Complete()
    {
        _context.SaveChanges();
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
    private readonly TinyBooksDbContext _context;
    private readonly DbSet<TEntity> _dbSet;
    private bool _disposed;

    public GenericRepository(TinyBooksDbContext context)
    {
        _context = context;
        _dbSet = _context.Set<TEntity>();

    }

    // C
    public virtual void Add(TEntity entity)
    {
        _dbSet.Add(entity);
    }

    public virtual IEnumerable<TEntity> GetAll()
    {
        return _dbSet.ToList();
    }

    // R
    public virtual TEntity GetById(object id)
    {
        return _dbSet.Find(id);
    }

    // U
    public virtual void Edit(TEntity entity)
    {
        _dbSet.Attach(entity);
        _context.Entry(entity).CurrentValues.SetValues(entity);
    }


    // D
    public virtual void Remove(object id)
    {
        var entity = _dbSet.Find(id);
        Remove(entity);
    }

    public virtual void Remove(TEntity entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
            _dbSet.Attach(entity);

        _dbSet.Remove(entity);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class AccountsRepository : GenericRepository<Account>, IAccountsRepository
{
    private readonly TinyBooksDbContext _context;
    private bool _disposed;

    public AccountsRepository(TinyBooksDbContext context) : base(context)
    {
        _context = context;
    }

    public IEnumerable<Account> GetForUser(string applicationUserId) =>
        _context.Accounts.Where(a => a.ApplicationUserId == applicationUserId).ToList();

    public string GetAccountName(int accountId) =>
        _context.Accounts.SingleOrDefault(a => a.Id == accountId).Name;
}
2
If you are passing in your context to this structure, then its none of these classes responsibility to dispose it. This will lead to inconsistent and magic results which will draw the ire and criticism of your colleagues and future programmers - TheGeneral
Also i feel it my duty to warn you, UOW and Repository over EF (which is already both those things), is adding an extra level of abstraction and complexity which is seldom needed - TheGeneral
It is the responsibility of the container that created and injected the dependency to control the life time of the dependency - Nkosi
Is this a web app? WPF? Something else? - mjwills

2 Answers

3
votes

Generally speaking the creator of the context should dispose of it.

Don't dispose of the context in classes that you pass it in as this will confuse other developers who may use the context after it's disposed.

In your example the repos should not dispose of the context - they don't own it.

1
votes

You can initialize your context during its declaration inside UnitOfWork class and its lifetime will depend on the lifetime of UnitOfWork class.

public class UnitOfWork : IDisposable, IUnitOfWork
{
    private readonly TinyBooksDbContext context = new TinyBooksDbContext();

    ......

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

This way, your context will be disposed with your UoW instance. You should not have dispose methods inside your generic repository.