Skip to main content
Code Review

Return to Revisions

4 of 4
Commonmark migration

I leaked Entity Framework into my domain services, because [...]

Fail. The only reason you would ever want to wrap EF with your own repository+unit-of-work implementation, would be to make an interface between EF and your code, possibly to enable swapping EF for something else at one point or another. By leaking it, you defeat the entire purpose of the additional abstraction level, and are left with a tremendously over-engineered solution for something that should be simple.

Rewind.

Back in the days of .net 2.0 and ADO.NET SqlConnection and SqlCommand boilerplate, there was a need to abstract away all the bolts and nuts of database interaction. A clean solution was the repository pattern, and with a unit of work to encapsulate a transaction, code that needed to interact with a database was nicely shielded from the inner workings of ADO.NET - and the abstraction layer made it child's play to swap a MySQL backend for an SQL Server Express backend: none of the business-layer code needed to change!

Enter EF.

Entity Framework abstracts away the SqlConnection and SqlCommand boilerplate, and its DbContext cleanly encapsulates a SqlTransaction - nothing is committed until you call SaveChanges(). By changing pretty much nothing, one can swap a MySQL backend for an SQL Server Express backend: none of the data-access-layer code needs to change!

What does that tell us? Entity Framework is a unit-of-work/repository pattern, abstracting away a truckload of boilerplate.

By abstracting EF with a unit-of-work/repository pattern, you've abstracted a unit-of-work/repository with a... unit-of-work/repository, and one that's very much more limited and, no offense, clumsy.

IQueryable<T> Select();

That member, all by itself, makes every other one moot. Exposing IQueryable<T> defers the query execution to the calling business layer code, which may not even be iterating the results and pass them to the view - in which case execution is deferred to the view. Even better: if the view contains code that filters the results, that filter may end up executing on the database backend. And if the view or business code uses functions that would be valid with Linq-to-Object, but that the EF provider doesn't support or can't translate into valid SQL, you've got a bug that you didn't need (well, who needs a bug eh?)...


In my unit of work, how should I handle rollbacks?

You don't. Client (business) code disposes the DbContext without calling SaveChanges. That's all.

How do I go about unit testing this without writing an integration test?

You mock IDbSet<T> and the interface your DbContext is implementing. Simple as that.


The idea behind abstracting the database stuff, is that you need to be able to test/run your business code without actually hitting the database. I use something like this:

public interface IUnitOfWork
{
 void Commit();
 IDbSet<T> Set<T>();
}

The IDbSet<T> can be mocked (you Setup the return value of Set<T>()), and by coding against this IUnitOfWork, you have an abstraction you can use to mock your DbContext:

public class MyContext : DbContext, IUnitOfWork
{
 public void Commit()
 {
 SaveChanges();
 }
 public IDbSet<Foobar> Foobars { get; set; }
 // ...
}

The "repositories" become an implementation detail, they're just for EF to pick up: your code is working against an IDbSet<TEntity> - does that not look just about exactly like a generic repository?

So does this mean EF should be tightly coupled to my domain layer [...]?

If you're coding against IUnitOfWork, you have a loose enough coupling to test everything you need to test, without hitting a database. Yes, you're using an interface (IDbSet<T>) that is essentially some EntityFrameworkRepository<TEntity>... but to swap for a NHibernateRepository<TEntity> you'd have to reimplement all the seam-level code anyway, leaked or not; the business-layer code only ever needs to see/use an IEnumerable<TEntity>, and the TEntity in question. By reusing your entity classes, and only ever exposing a group of them as an IEnumerable<TEntity>, you pretty much shield your business code from even noticing that it's no longer Entity Framework providing them with this TEntity.

Mathieu Guindon
  • 75.5k
  • 18
  • 194
  • 467
default

AltStyle によって変換されたページ (->オリジナル) /