Before posting this, I have spent the past 4 days tirelessly investigating the Internet and the different Stack Exchange websites on how to correctly implement the Unit of Work pattern and Unit Test it using MVC 5 + EF 6.1. So far, the implementation I manage to make it work was this one.
Here's the actual problem:
I am lifting all the lookups of the Database to the Unit of Work (as I've understood). I have a repository called "TournamentRepository" which derives from a GenericRepository
with the generic methods. In the example above, it uses a neat trick on implementing statics as extensions so we don't need to create new Interfaces for each new Repository.
public static async Task<bool> IsUserRegisteredInTournament(this IGenericRepository<PGTournament> tournamentRepository, string tournamentUrl, string userId)
{
return await tournamentRepository.Get().Select(x => x.Participants.Where(y => y.UserId == userId))
.AnyAsync();
}
What I want to emphasize is the Get()
method. I want to know if it is a leaky abstraction.
It is actually from the following:
public class GenericRepository<TEntity>: IGenericRepository<TEntity> where TEntity:class
{
private readonly IDbSet<TEntity> _iDbSet;
private readonly DbSet<TEntity> _dbSet;
//protected IDbSet<TEntity> Entities { get; set; }
public GenericRepository(IDbRepositories dbRepositories)
{
_iDbSet = dbRepositories.Set<TEntity>();
_dbSet = dbRepositories.SetDb<TEntity>();
}
public IDbSet<TEntity> Get()
{
return _iDbSet;
}
public async Task<TEntity> FindAsync(int id)
{
return await _iDbSet.FindAsync(id);
}
public async Task<IEnumerable<TEntity>> GetAllAsync()
{
return await _iDbSet.ToListAsync();
}
public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return _iDbSet.SingleOrDefault(predicate);
}
public void Add(TEntity entity)
{
_iDbSet.Add(entity);
}
public void AddRange(IEnumerable<TEntity> entities)
{
_dbSet.AddRange(entities);
}
public void Remove(TEntity entity)
{
_iDbSet.Remove(entity);
}
public void RemoveRange(IEnumerable<TEntity> entities)
{
_dbSet.RemoveRange(entities);
}
/*
*
* Creates a wrapper for Entity Framework's Select Method
*/
public IQueryable<T> Select<T>(Func<TEntity, T> func ) where T: class
{
return _iDbSet.Select(func).AsQueryable();
}
}
Whenever I've tried to do a Select
at the repository, I would get no IntelliSense because the object is layered through the interface.
public interface IGenericRepository<TEntity> where TEntity : class
{
IDbSet<TEntity> Get();
Task<IEnumerable<TEntity>> GetAllAsync();
Task<TEntity> FindAsync(int id);
// This method was not in the videos, but I thought it would be useful to add.
TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate);
void Add(TEntity entity);
void AddRange(IEnumerable<TEntity> entities);
void Remove(TEntity entity);
void RemoveRange(IEnumerable<TEntity> entities);
}
I tried implementing my own Select
. The only problem was when I tried to unit test and I ran an Async()
method. For unit testing an Async()
method, we should follow this.
Unfortunately, I was returning an IQueryable<T>
and it did not go through all the conversions that MS does in their TestDbAsyncQueryProvider
.
After hours of banging my head, I figured out that I could return the IDbSet<>
and do the Select
manually.
I rebranded the Get()
method for it to return the current IDbSet<>
.
Another note
You may have seen that I have a DbSet<>
and an IDbSet<>
. The reason is (hope you can correct me if I'm wrong), is that RemoveRange
and AddRange
lack this implementation in IDbSet<>
(which I'm using for unit testing, if not, I would receive the un-overridable error for the property not being virtual). So for me, the only way was to actually get the Entity and pass it as both IDbSet
and DbSet
.
IGenericRepository
because you didn't implement all methods inGenericRepository
and also added methods to the latter. Please show the code as complete as possible and in the state you want it reviewed, so without tentative methods. Of course you can propose alternatives in separate snippets. \$\endgroup\$