I've seen various blog posts (and much conflicting advice) about the repository pattern, and so I'll start by saying that the code below is probably not following the repository pattern in many people's opinion. However, it's a common-enough implementation, and whether it adheres to Fowler's original definition or not, I'm still interested in understanding more about how this implementation is used in practice.
Suppose I have a project where data access is abstracted via an interface such as the one below, which provides basic CRUD operations.
public interface IGenericRepository<T>
{
void Add(T entity);
void Remove(T entity);
void Update(T entity);
IEnumerable<T> Fetch(Expression<Func<T,bool>> where);
}
Further suppose that I have a service layer built atop that, for example:
public class FooService
{
private IGenericRepository<Foo> _fooRespository;
...
public IEnumerable<Foo> GetBrightlyColoredFoos()
{
return _fooRepository.Fetch(f => f.Color == "pink" || f.Color == "yellow");
}
}
Now suppose that I now need to know how many brightly colored Foo
s there are, without actually wanting to enumerate them. Ideally, I want to implement a CountBrightlyColoredFoos()
method in my service, but the repository implementation gives me no way to achieve that other than by fetching them all and counting them - which is potentially very inefficient.
I could extend the repository to add a Count()
method, but what about other aggregate functions that I might need, such as Min()
or Max()
, or Sum()
, or... you get the idea.
Likewise, what if I wanted to get a list of the distinct Foo colors (SELECT DISTINCT
). Again, the simple repository provides no way to do that sort of thing either.
Keeping the repository simple to make it easy to test/mock is very laudable, but how do you then address these requirements? Surely there are only two ways to go - a more complex repository, or a "back-door" for the service layer to use that bypasses the repository (and thus defeats its purpose).
I would say you need to change your design. What you want to do is have one "main" generic repository that has your basic CRUD, but also smaller repositories for each entity. You will then just have to draw a line on where to place certain operations (like sum, count, max, etc.) Most likely not all your entities are going to have to get counted, summed, etc. and most of the time you won't be able to add a generic version that applies to all entities for aggregate functions.
Base Repository:
public abstract class BaseRep<T> : IBaseRep<T> where T : class
{
//basic CRUD
}
Foo Repository:
public class FooRep : BaseRep<Foo>, IFooRep
{
//foo specific functions
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With