Entity Framework Context Abstraction

Chris Eargle / Wednesday, January 14, 2015

Depending on a project’s size, scope, and requirements, you may have opted to use an ORM such as Entity Framework without further abstraction or encapsulation. Larger enterprise applications may need complicated layering and services, but the development overhead should be justified.

Instead of lecturing on when to use more abstractions on top of the data access and object-relational abstractions provided by the ORMs, I want to address a common problem: the abstraction becoming necessary on an existing project.

This article primarily addresses Entity Framework, but much of it is applicable to other ORMs. My approach is to avoid a rewrite and minimize code impact.

Repositories Everywhere

Some people won’t even admit to using an EF context in an ASP.NET MVC controller, an MVVM view model, or a presenter. Mention it in a forum or conference discussion and you’re likely to be shamed for not using a particular silver-bullet pattern: Repository.

It is a fine pattern, and you’re already using it. However, you’re not using that person’s preferred interface (e.g. IRepository). Some versions will cause massive amounts of rewriting. Welcome to “refactoring” sprints 1, 2, and 3!

Avoid this outcome by considering what the Repository pattern represents.

“A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection” – P of EAA Catalog

The caller of the repository is only aware of some kind of collection and should be unconcerned about the repository’s data storage. Entity Framework handles data mapping, and the repository should have some way to accept query criteria as the amount of data is likely nondeterministic.

 DbSet is a Repository.

The problem isn’t the lack of a repository; it’s the concrete coupling. Change your context class’ DbSet properties to IDbSet instead. If you have any compile-time errors, make sure you include a using clause for System.Data.Entity. The concrete class derives from DbQuery, but most of its extra methods are also available as extension methods to IDbSet. Bringing them into scope will do the trick.

Context Interface

The MSDN documentation describes DbContext as a repository. This is accurate, but an application-specific context is more like a repository bag.

 1 public class AppContext : DbContext
 2 {
 3     public IDbSet<Department> Departments { get; set; }
 4     public IDbSet<Employee> Employees { get; set; }
 5     public IDbSet<Person> People { get; set; }
 6 }

Derived classes define properties for the various entity types it handles, and callers use those properties: context.Employees is more convenient and readable than context.Set(). This is good, but DbContext is much more than a repository bag. Part of this abstraction requires determining and defining only the necessary pieces.

The initial interface can be extract completely from the concrete class. However, DbContext also represents the Unit of Work pattern, so the context is also responsible for saving and discarding changes. Saving data uses an explicit method call, and if it is never made, changes are discarded using the disposable pattern.

 1 public interface IAppContext : IDisposable
 2 {
 3     IDbSet<Department> Departments { get; set; }
 4     IDbSet<Employee> Employees { get; set; }
 5     IDbSet<Person> People { get; set; }
 6     int SaveChanges();
 7     Task<int> SaveChangesAsync();
 8 }
10 public class AppContext : DbContext, IAppContext
11 {
12     public IDbSet<Department> Departments { get; set; }
13     public IDbSet<Employee> Employees { get; set; }
14     public IDbSet<Person> People { get; set; }
15 }

Replacing explicit declarations of AppContext with IAppContext will expose DbContext dependencies. Add the missing pieces to the interface for a clean build and a record of the dependencies.

What’s Next

This first bit of interface extraction was fun, but it also provides flexibility for solutions using a variety of object-oriented design patterns. I will cover the most important task in my upcoming article: removing the concrete context dependencies.