Every article, old or new, about Entity Framework tells you the same thing: DbContext is already a Unit of Work, DbSet is already a Repository, and if you abstract over them you're just creating abstraction over abstraction. I think they're answering the wrong question.
Others say you should only do if the app is big or logic is complex. I think it doesn't matter the size of your organization or product! You should always abstract infrastructure.
Let's see if I can convince you.
Current misconception
If you search right now about EF DBContext Repository Pattern, you are likely to find the following:
- Argues that DbContext is already a Unit of Work and DbSet is already a Repository. Adding another layer is redundant abstraction over abstraction. — Gunnar Peipman
- A thorough analysis concluding that the traditional repository pattern adds complexity without benefit — Jon P Smith (The Reformed Programmer)
- The Repository Pattern is Dead. EF Core Killed It. — Yaseer Arafat
- Discusses how developers fall into the trap of wrapping DbContext, ending up with more code, less flexibility, and no real benefit. — Julio Casal
- And the list goes on...
When we summarize the arguments from most of those posts, most of them can fall into the following categories:
- DbContext is the Unit of Work; DbSet is the Repository. Wrapping them is abstraction over abstraction.
- You lose EF IQueryable composition, eager/lazy loading, change tracking, raw SQL, bulk operations all get hidden or crippled.
- "Swap out the ORM" never happens. Too risky to swap ORMs in real projects.
I will talk about why those things aren't entirely true.
Fallacy #1: Abstraction over abstraction
The people making this argument are answering the wrong question. They're asking:
Should I abstract Entity Framework?
When the real question is:
Should my domain/application layer know how or where data is stored?
The Repository pattern was never about wrapping an ORM!
The original https://deviq.com/design-patterns/repository-pattern/ is defined as a collection-like abstraction that hides persistence completely, lives in the domain layer as an interface, and speaks the language of the business. It predates ORMs and EF!
Saying "DbContext is already a repository" combines two different things:
- A data access mechanism (what DbContext is)
- A domain boundary contract (what a Repository is supposed to be)
A DbContext speaks the language of SQL, change tracking, Include(), IQueryable, and migrations. A proper repository speaks the language of your domain: GetActiveCustomersByRegion(), FindOverdueInvoices().
DbContext is Microsoft's abstraction, not yours
Being too reliant on DbContext and DbSet<T> is giving too much power to the ORM library. They are owned by Microsoft, not your application!
You don't control them. You don't own their interface. They can change across versions.
Your domain contract (IOrderRepository) is yours. You define it. You control what it exposes. You decide when it changes. This is the difference between:
- Coupling to a third-party API surface you don't control
- Depending on a contract you own that happens to be implemented by EF today
So even though Microsoft tries their best in keeping contracts backwards compatible, we've seen breaking changes between EF versions, like:
- FromSql renamed to FromSqlRaw / FromSqlInterpolated
- DbQuery removed — replaced by keyless entity types
- Default string length changed in the backing model
- DbContext.Entry behavior changed for detached entities
- DbSet no longer implements IAsyncEnumerable — code casting DbSet to IAsyncEnumerable broke
Do you still want to have to change your whole application whenever a contract changes?
Fallacy #2: Lose EF specific features
This argument fundamentally misunderstands what abstraction means. Abstraction doesn't mean you stop using EF features. It means you stop exposing them.
The repository implementation still uses every EF Core feature available. Include(), ThenInclude(), AsSplitQuery(), change tracking, ExecuteUpdate(), raw SQL, everything. The abstraction is the interface, not the implementation.
The biggest "feature" people claim you lose is leaking IQueryable to consumers. But as https://ardalis.com/avoid-dbcontext-iqueryable-proliferation/ mentioned, letting IQueryable proliferate through your app is actively harmful:
- Any consumer can compose any arbitrary query — there's no control over what SQL hits your database
- Query logic gets scattered across controllers, services, and middleware instead of being centralized
- You can't optimize, cache, or audit queries you don't know exist
- You can't swap the data source for specific queries (cache, Dapper, API) because consumers are writing LINQ everywhere
Encapsulating IQueryable inside the repository means:
- All query logic is in one place: easier to optimize, profile, and review
- The domain gets exactly the data it needs: no over-fetching, no surprise N+1s
You don't lose EF features by abstracting. You stop accidentally exposing EF's entire surface area as your application's data access contract. The implementation uses everything EF offers. The interface exposes only what the domain actually needs.
That's not losing features. That's engineering.
Fallacy #3: You will never replace EF
This is the argument I hear most, and it misses the point entirely.
Even though I agree it is hard for business to move from, say MongoDB to SQL Server, it's not impossible.
What is more likely to happen is moving from EF6 to EF Core.
That happened and if you were too entangled with EF6 in your services, controllers, you probably struggled way more than necessary!
"You will never replace EF" is an argument about probability. But abstraction isn't insurance against a single catastrophic event. It's a daily engineering benefit that makes your code testable, traceable, flexible query-by-query, and resilient to the breaking changes Microsoft ships with every major version.
Conclusion
The debate was never about Entity Framework. It was about whether your business logic should know where its data comes from.
A repository interface is a few lines of code. Behind it, you can use every feature EF offers, and when EF ships its next round of breaking changes, you fix the implementation, not the entire application.
If you're working in a codebase where DbContext is threaded through controllers and services, you don't have to rewrite everything tomorrow. Start with one aggregate. Define the interface. Move the queries behind it. See how it feels. The cost is small, and once you've done it, you won't want to go back.
I hope I've convinced you, but if I haven't, tell me why in the comments!
Top comments (0)