The generic repository is just a lazy anti-pattern
A generic repository is often used with the entity framework to speed up the process of creating a data layer. In most cases this is a generalization too far and it can be a trap for lazy developers.
A generic repository often looks something like the code below. It defines generic methods for the most common types of data operation, such as updating, fetching and deleting. It’s appealing to developers because it’s simple, flexible and allows you to crank out a large domain model without having to write a lot of code.
public interface IRepository<T> { IEnumerable<T> GetAll(); IEnumerable<T> Find(Expression<Func<T, bool>> query); T GetByID(int id); void Add(T item); void Update(T item); void Delete(T item); }
The problem is that this is not a neat and convenient abstraction, but rather a time-saving short-cut that can undermine the coherence of a solution in a number of respects.
It’s a leaky abstraction
Martin Fowler defines a repository as an “object that mediates between the domain and data mapping layers”. The objective is to isolate domain objects from details of the data access code.
However, the generic repository can allow developers to wrap the underlying data access technology such as Entity Framework classes. This allows a direct dependency from the data access technology to leak out into the application logic.
A repository should abstract the entire data access layer and enforce the concealment of details such as the database engine or data access technology. This generic implementation does not necessarily isolate anything – it’s just adding a pointless and leaking abstraction with no guaranteed benefits.
It’s too much of a generalization
Most repositories need “Save” and “Delete” methods… well, actually, do they? One objection to the generic repository is that it’s just plain lazy as the developer has not taken the time to consider how any consuming classes will use it. For example, are there any specialized fetch methods you need that may support, say, pagination? Will the repository specialize in reading or updating the model?
Very few real-world domain models can be addressed by the same set of methods. You should consider how you want data to be used. Once you start trying to serve more specialized business requirements then the generic repository quickly starts to look inadequate.
It doesn’t define a meaningful contract
The repository should represent a contract between the domain objects and a data store. It defines the kinds of operations that a data store is expected to service. A weakness of the generic repository is that it defines this contract so widely that it becomes almost meaningless.
The first line of code below illustrates the kind of search method that is often found in generic repositories. It provides a lot of flexibility over how you might query data, but it’s impossible to tell what form of contract this represents. It could be asking the data store to return pretty much anything.
IQueryable<T> Find(object query); IQueryable<Customer> FindCustomerByName(string name);
The second line represents something far more concrete. It clearly defines the relationship between domain objects and data store. Apart from the certainty of the contract, it provides a far more readable implementation.
A generic repository does have a place… just not on the frontline
Nobody likes to repeat themselves, but a generic repository interface is an over generalization. That said, there’s nothing to stop you from using a generic repository class as part of a more specific repository implementation. This will help you to derive the re-use benefits of a generic repository whilst still providing a strongly-defined contract.
The code below shows a simple example where a generic Repository<T> class marked for internal access is used within a public-facing repository implementation. This composition approach cuts down on the amount of code you have to write whilst still providing a meaningful public-facing contract.
public class CustomerRepository { private Repository<Customer> internalRepository; public IEnumerable<Customer> FindCustomerByName(string input) { return internalRepository.FindBy(c => c.Name == input); } }
A generic repository does have a place, but as a helper rather than a contract. The trick is not to expose a generic interface but achieve re-use through composition.