144

As i dig deeper in to the DbContext, DbSet and associated interfaces, I am wondering why you would need to implement a separate "Generic" Repository around these implementations?

It looks like DbContext and IDbSet do everything you need and include the "Unit Of Work" inside DbContext.

Am I missing something here or does it seem people enjoy adding another layer of dependency for no reason.

Cœur
  • 32,421
  • 21
  • 173
  • 232
Code Jammr
  • 1,867
  • 3
  • 14
  • 18
  • This is bit disputed/opinion-based issue. I have discussed this [here](https://stackoverflow.com/a/51781877/5779732). – Amit Joshi Sep 11 '19 at 07:42

3 Answers3

201

You are actually right. DbContext is an implementation of the unit of work pattern and IDbSet is an implementation of the repository pattern.

Repositories are currently very popular and overused. Everybody use them just because there are dozens of articles about creating repository for entity framework but nobody actually describes challenges related to this decision.

Main reasons for using repository are usually:

  • Hide EF from upper layer
  • Make code better testable

The first reason is some kind of architectonic purity and great idea that if you make your upper layers independent on EF you can later on switch to other persistence framework. How many times did you see such thing in the real world? This reason makes working with EF much harder because your repository must expose a lot of additional features wrapping what EF allows by default.

In the same time wrapping EF code can keep your code better organized and following Separation of concern rule. For me this can be the only real advantage of repository and unit of work but you have to understand that following this rule with EF will maybe make your code better maintainable and better readable but in the initial effort to create your application will be much higher and for smaller applications this can be unnecessary complexity.

The second reason is partially correct. The big disadvantage of EF is rigid architecture which can be hardly mocked so if you want to unit test upper layer you must wrap EF somehow to allow mocking its implementation. But this has many other consequences which I described here.

I follow Ayende's blog. If you ever used NHibernate you probably know his articles. This guy recently wrote several articles against using repository with NHibernate but NHibernate is much better mockable.

Community
  • 1
  • 1
Ladislav Mrnka
  • 349,807
  • 56
  • 643
  • 654
  • So if you wanted to create mocks for EF I see they give you the IDbSet interface. It looks like a bunch of work to create fakes but once you got it done it would be a great piece of reusable code. – Code Jammr Apr 11 '11 at 20:23
  • 3
    You can mock `IDbSet` you can also define custom interface in your derived context but thats all. Once your code uses ChangeTracker, Entries or what ever else it will require big effort to wrap them all. – Ladislav Mrnka Apr 11 '11 at 20:25
  • As this technology gets more mature we will start seeing some stuff comitted to take care of this headache for us. I am just studying the EF 4.1 stuff for a project. I was told it was "the way to roll". It looks fairly straightforward, but the performance is terrible. Ahh the sacrifice for performance over features. – Code Jammr Apr 11 '11 at 20:31
  • 1
    Yes EF is not very performance oriented tool. At least MS has a lot of opportunities to make this better in future versions. – Ladislav Mrnka Apr 11 '11 at 20:33
  • 1
    46ms on SqlDataReader compared to 346ms on EF.... I saw that on some Chart when googling eralier. That is a big hit. – Code Jammr Apr 11 '11 at 20:39
  • @Ladislav, re mocking: Yes, *if* your code uses ChangeTracker. But if sticking to the repository pattern, the code should only use the repository methods, even if the repository pattern is implemented by IDbContext and IDbSet. You'd just add methods to IDbContext that encapsulate the ChangeTracker stuff. Right? – chiccodoro Aug 18 '11 at 09:03
  • 2
    @chiccodoro: Right. But once your mocked class exposes `IQueryable` or accepts `Expression<>` as parameter which is internally put to Linq-to-entities query you are defining logic outside of the mocked component with side effects which cannot be tested with unit tests. – Ladislav Mrnka Aug 18 '11 at 09:20
  • @Ladislav: Are you saying that the linq-to-entities part is not testable? That's in fact true but no different from any other implementation of the repository pattern. Since the O/R mapping happens behind the scenes of the repository abstraction layer and is exactly the part which is replaced by a mock when unit-testing. But I probably got your point wrong. (+1 BTW) – chiccodoro Aug 18 '11 at 09:35
  • 8
    If I'm using DbSet and BdContext straight out there in my business layer I have to reference EntityFramework.dll there as well as in my DataLayer project. That alone tells me that it needs some sort of wrapping. – Ingó Vals Oct 03 '11 at 10:30
  • 1
    Hi Ladislav, what do you think of this alternate solution provided by Daniel. http://blogs.clariusconsulting.net/kzu/how-to-design-a-unit-testable-domain-model-with-entity-framework-code-first/ It is still testable and maintainable I believe but what is your opinion about this approach not using Repository/UoW pattern? – 123 456 789 0 Jun 04 '12 at 13:36
  • 2
    downvote: incomplete - abstracting EF behind a repository interface can make the exact same client code run in both SL and WPF. – h.alex Feb 09 '13 at 21:35
21

I am struggling with the same issues, and mockability for unit testing of the EF layers is important. But I ran across this great article which explains how to set up the EF 4.1 DbContext to be mockable by making sure your derived DbContext implemented a generic interface and exposes IDbSet rather than DbSet's. Since I am using a Database First approach, because our database already exists, I simply modified the T4 templates used to generate my derived DbContext to generate it to return IDbSet interfaces, as well as derive from my generic interface. That way the entire thing can be easily mocked, and you don't need to implement your own Unit Of Work or repository pattern. Just write your service code to consume your generic interface, and when you go to unit test it, just mock the generic interface with specific test data and you are good to go.

http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic-repository/

TheVillageIdiot
  • 38,082
  • 20
  • 126
  • 184
Kendall Bennett
  • 2,046
  • 1
  • 15
  • 16
5

One reason for creating the repository is so you can hide the implementation of DBSet and DbContext if you decide to move from EntityFramework to something else or vice versa.

For example, I was using NHibernate and I wrapped all of the calls to that framework inside my repository classes. They return IEnumerable for their gets to be "generic" and my repositories have the standard CRUD operations (update, delete, etc). I have long since moved to Entity Framework. Upon doing so, I did not need to change anything in my ViewModel classes or beyond because they pointed to my repository--I only needed to change the inside of my repository. This made life much easier when migrating.

(I used NHibernate because we are connecting to the ISeries, and at the time, there were no cost affective implementations using EF with the ISeries. The only one available was to pay $12,000 to IBM for their DB2Connect)

Leniel Maccaferri
  • 94,281
  • 40
  • 348
  • 451
  • "Almost" (on the subject of hiding DBSet and DbContext) you will find that you don't need to expose EF to any consumers (for example if you leverage DI) but you do need an interface that exposes IDbSet properties or go one step further and instead type all of your properties as IQueryable's, but my point is that you can completely hide your dependence on DbSet and DbContext. CRUD ops can then be written as extension methods, you can write multiple extension methods for different backing stores. You would not, however, be hiding the use of LINQ. – Shaun Wilson Jan 19 '13 at 01:48