Firstly, be clear about your goal. The repository pattern has (at least) 3 key reasons for existence:
1) To abstract away the data layer. As far as EF is concerned, if this is your goal then the repository pattern is no longer beneficial. Trying to abstract apps from Entity Framework is far more trouble than it is worth. You will end up with either/both a crippled DAL where the awesomeness that EF can provide is not available or inefficient/slow, or a very complex bunch of repository methods with expressions and other nastiness as parameters. Attempting to abstract your application away from EF (in case you might want to change to another ORM for instance) is pointless. Accept EF as you would accept the fact you're writing your application in .Net. To abstract out EF to a point it could be replaced you may as well not use it because you won't see any of the benefits EF can actually provide.
2) To make business logic easier to test. IMO This is still a valid argument for repositories with Entity Framework. Yes, EF DbContexts can be mocked, but they are still a messy business. Mocking repositories is no harder than any other dependency.
3) TO serve as a domain gatekeeper. Patterns such as DDD look to lock away actions against data in domain objects and services. The repository pattern can assist with this when using EF to help contain methods that are responsible for manipulating the domain. For pure DDD I wouldn't recommend them, though I wouldn't recommend using Entities as DDD domain objects either. I do use the repository pattern to manage the C.R. and D. aspects of CRUD and rely on view models to encapsulate domain logic around U.
The repository pattern I've found to be the most use in serving points 2 & 3 is to do away with the very common concept of generic repositories, and rather treat the repository more in-line with how you would treat a Controller in MVC. Except instead of between View and Model, between Model and Data. The repository is a class that serves a Controller (in MVC) in that it is responsible for creating, reading, (at a core level) and deleting entities. This pattern works very well in conjunction with unit of work. (https://github.com/mehdime/DbContextScope is the implementation I adopt.)
In Creating an entity it is responsible for ensuring all required (non-null-able) values and references are provided, returning an entity associated to the DbContext, ready to go. Effectively an entity factory. You could argue separation of concerns, though given the repository already has access to the DbContext to retrieve related entities it's a case of being pretty much the best place for the job.
In Reading entities it provides a base reference to further query entities by providing an IQueryable<TEntity>
, enforcing core level rules such as .Where(x => x.IsActive)
for soft-delete scenarios, or filters for authentication/authorization/tenancy based on dependencies that can reveal the current user for example. By exposing IQueryable
you keep the repository implementation simple and give the consumer (Controller) control over how the data is consumed. This can leverage deferred execution to:
- Select only the data needed for the view models.
- Perform counts and exists checks. (
.Any()
)
- Customize filtering logic across the entity structure for the specific use case.
- Perform pagination.
- Grab as much (.ToList(), .Take()) or as little (.SingleOrDefault(), .FirstOrDefault()) data as needed.
Read methods are extremely easy to mock, and keep the repository implementation footprint quite small. Consumers need to be aware that they are dealing with Entities, and deal with the nuances of EF and it's proxies, but the consumer is the guardian of the Unit of Work (lifespan of the DbContext) so hiding this fact away from it is rather moot. Passing complex query expressions into repository methods as parameters leaves consumers equally responsible for knowing about EF's nuances. An expression that calls a private method fed to a generic repository to go in a Where clause will break things just as fast. Going down this route trips point #1 above, don't abstract away EF from your app.
In Deleting an entity it ensures that entities and their associations are properly managed, whether hard or soft deletes.
I avoid generic repositories because just as a Controller (and view) will deal with any number of related domain view models, this means that they will need to deal with a number of related data entities. Operations against any one entity will invariably be tied to operations against other dependents. With generic repositories that separation means a) an ever growing number of dependencies and b) generic methods that do trivial crap and a lot of custom code to handle the meaningful stuff, or complex code to try and facilitate it in a generic (base) way. By having a single repository per controller, and perhaps some truly common shared repositories for common entities. (look-ups for example) my repositories are designed explicitly to serve one area of an application and have only one reason to change. Sure, there may be 2 or more screens that require the same behavior from a repository, but as those aspects of an application or service mature, their repositories can mature/optimize as needed without side-effects. SRP and KISS easily trump DNRY.
Generic classes in general have their uses, but in almost any case where I see developers write them, I would argue that it is premature optimization. Start with non-generic implementations, then as a product matures, optimize generics into the code rather than attempting to design architecture around them. The result is almost invariably the realization that you need to de-optimize them out or get "clever" about working around discovered limitations where the patterns hinder development.
Anyhow, some food for thought other than "repositories aren't needed with EF" :)