niklodeon
niklodeon

Reputation: 1390

Generic UnitOfWork

I am trying to create a Repository & UnitOfWork for Data Access Layer. In my current implementation I have to modify my UnitOfWork everytime I create a new repository. I would like to avoid that and also keep the functionality to extend my repository abstract class.

Following is my generic Repository & UnitOfWork interface & classes

public interface IRepositoryBase<T> where T : class
{
    IList<T> FindAll();
    T FindByCondition(Expression<Func<T, bool>> expression);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}

public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
    protected DBContext _dbContext { get; set; }

    public RepositoryBase(DBContext dbContext)
    {
        _dbContext = dbContext;
    }
    //other methods removed
    public void Create(T entity)
    {
        _dbContext.Set<T>().Add(entity);
    }
}

public interface IUnitOfWork 
{
    IReminderRepository Reminder { get; }
    void Save();
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    protected DBContext _dbContext { get; set; }
    private IReminderRepository _reminderRepository;

    public UnitOfWork(DBContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReminderRepository Reminder
    {
        get
        {
            return _reminderRepository = _reminderRepository ?? new ReminderRepository(_dbContext);
        }
    }

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

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}

Here I can extend my Repository as per my specific needs by implementing the specific Repository as

public interface IReminderRepository : IRepositoryBase<Reminder>
{
    IList<Reminder> GetAllReminders();
    Reminder GetReminderById(Guid id);    
    Reminder GetReminderByName(string name);    
    void CreateReminder(Reminder reminder);    
    void UpdateReminder(Reminder reminder);    
    void DeleteReminder(Reminder reminder);    
}

public class ReminderRepository : RepositoryBase<Reminder>, IReminderRepository
{
    public ReminderRepository(DBContext dbContext)
    : base(dbContext)
    {
        _dbContext = dbContext;
    }
    //other methods removed
    public Reminder GetReminderByName(string name)
    {
        return FindAll()
            .OrderByDescending(r => r.Name)
            .FirstOrDefault(r => r.Name == name);

        //return FindByCondition(r => r.Name == name);
    }
}

This is ok but when ever I will create a new Specific Repository I will have to modify the UnitOfWork class as well by adding a new property for the new Repository.

While searching online I found following but it does not work in my case as my RepositoryBase is an abstract class.

public interface IUnitOfWork 
{
    void Save();
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly DBContext _dbContext { get; set; }
    private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>();

    public Dictionary<Type, object> Repositories
    {
        get { return _repositories; }
        set { Repositories = value; }
    }

    public UnitOfWork(DBContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IRepositoryBase<T> Repository<T>() where T : class
    {
        if (Repositories.Keys.Contains(typeof(T)))
        {
            return Repositories[typeof(T)] as IRepositoryBase<T>;
        }

        IRepositoryBase<T> repo = new RepositoryBase<T>(_dbContext);//This does not work
        Repositories.Add(typeof(T), repo);
        return repo;
    }

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

Upvotes: 0

Views: 1271

Answers (1)

mm8
mm8

Reputation: 169370

You obviously need to get a reference to a IReminderRepository somewhere in your code to be able to use the remainder specific APIs.

If you don't want to extend your UnitOfWork class to return an IReminderRepository, you may create one yourself in the method that actually uses the specific repository, e.g.:

using (var context = new DBContext())
{
    IUnitOfWork uow = new UnitOfWork(context);
    ReminderRepository repository = new ReminderRepository(context);
    Reminder remainder = repository.GetReminderByName("...");
    remainder.SomeProperty = "updated value..";
    uow.Save();
}

The only purpose of using the unit of work is to be able to share the same context between several different repositories anyway. Exposing a Dictionary<Type, object> in your UnitOfWork won't solve anything as the purpose of using generics is to provide compile-time type safety.

Upvotes: 1

Related Questions