I have been reading a lot of articles explaining how to set up Entity Framework's DbContext so that only one is created and used per HTTP web request using various DI frameworks.
My question is...why is this a good idea in the first place? What advantages do you gain by using this approach? Are there certain situations where this would be a good idea? Are there things that you can do using this technique that you can't do when instantiating DbContexts per repository method call?
Please forgive my ignorance, but I'm really trying to understand why.
Let start by repeating Ian: Having a single DbContext is a bad idea, since it is not thread-safe, but I expect you already know that and just want to know what not just inject a new instance (thus with a transient lifestyle) of the DbContext into anyone who needs it.
Let me start by saying that registering a DbContext as transient could work, but typically you want to have a single instance of such a unit of work within a certain scope. In a web application, it is practical to define such a scope on the boundaries of a web request; thus a Per Web Request lifestyle. This allows you to let a whole set of classes operate within the same context. In other words, they operate within the same business transaction.
When you have no intention of letting a whole set of operations operate inside the same context, in that case the transient lifestyle is fine, but there are a few things to watch:
- Since every type gets its own instance, every class that changes the state of the system, needs to call
context.SaveChanges()(otherwise changes would get lost). This can complicate your code, and adds a second responsibility to the code (the responsibility of controlling the context), and is a violation of the Single Responsibility Principle. - You need to make sure that entities (loaded and saved by a
DbContext) never leave the scope of such a class, because they can't be used in the context of another class. This can complicate your code enourmously, because when you need those entities, you need to load them again by id, which could also cause performance problems. - Since
DbContextimplementsIDisposable, you probably still want to dispose all created instances. If you want to do this, you basically have two options. You need to dispose them in the same method right after callingcontext.SaveChanges(), but in that case the the business logic takes ownership of an object it gets passed on from the outside. The second option is to dispose all created instances on the boundary of the http context, but in that case you still need some sort of scoping to let the container know when those instances need to be disposed.
Another option is to not inject a DbContext at all. Instead, you inject a DbContextFactory that is able to create a new instance (I used to use this approach in the past). This way the business logic controls the context explicitly. If might look like this:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
The plus side of this is that you control the DbContext explicitly and it is easy to set this up. It also allows you to use a single context in a certain scope, which has clear advantages, such as running code in a single business transaction, and being able to pass around entities, since they originate from the same DbContext. The downside is that you will have to pass around the DbContext from method to method (which is called method injection). Note that in a sense this solution is the same as the 'scoped' approach, but now the scope is controlled in the application code itself (and is possibly repeated many times). It is the application who is responsible for creating and disposing that unit of work. Since the DbContext is created after the dependency graph is constructed, constructor injection is out of the picture and you need to revert to method injection.
Method injection isn't that bad, but when the business logic gets more complex, and more classes get involved, you will have to pass it from method to method, from class to class, which just complicates the code a lot (I've seen this in the past). For a simple application, this approach will do just fine though.
Because of the downsides this factory approach has for bigger systems, another approach can be useful and that is the one where you let the container or the infrastructure code / Composition Root manage that unit of work. This is the style that your question is about.
By letting the container or the infrastructure handle this, your application code keeps clean from having to create, (optionally) commit and dispose that instance, which keeps the business logic simple and clean (just a single responsibility). There are some difficulties with this approach. For instance, were do you commit and dispose that instance?
Disposing a unit of work can be done at the end of the web request. Many people however, incorrectly assume that this is also the place to commit the unit of work. However, at that point in the application, you simply can't determine for sure that the unit of work may actually be committed. When the application code threw an exception, you simply don't want to commit.
The real solution is again to explicitly program some sort of scope, but this time do it inside the composition root. When abstracting all business logic behind the command / handler pattern, you will be able to write a decorator that can be wrapped around each command handler that allows to do this. Example:
class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly DbContext context;
private readonly ICommandHandler<TCommand> decorated;
public TransactionCommandHandlerDecorator(
DbContext context,
ICommandHandler<TCommand> decorated)
{
this.context = context;
this.decorated = decorated;
}
public void Handle(TCommand command)
{
this.decorated.Handle(command);
context.SaveChanges();
}
}
This ensures that you only need to write this infrastructure code once. Any solid DI container allows you to configure such a decorator to be wrapped around all ICommandHandler<T> implementations.