The Entity Framework DbContext (or LINQ-to-SQL DataContext) is a Unit Of Work implementation. That means that the same DbContext should be used for all operations (both reading and writing) within a single web or service request. That means that there are a lot of different places where the DbContext have to be accessed. To avoid having to pass the DbContext around as a parameter, I’ve created a UnitOfWorkScope
that makes the DbContext ambient and easily accessible.
A common beginners problem when working with Entity Framework or LINQ-to-SQL is to have too short life times of the DbContext. A problem that I’ve seen many questions about on Stack Overflow is when questions are encapsulated in repositories or helper methods. Inside each method a new DbContext is created for that specific read. Later, when the returned entity has been updated and is to be saved the problem occurs. The entity should be saved using the same DbContext that once read it from the database to allow change tracking to work properly. Clearly, having separate DbContexts is a problem.
The first attempt to solve it is usually to pass the DbContext around. That only solves half the problem though, that of accessing it. The other half of the problem is to decide where to call
SaveChanges
to persist the changes done. Calling it from every method making changes spoils the entire unit of work concept. Trusting the creator of the context to know when any of a myriad of called functions have made changes seems risky.
I’ve been looking for a better way to handle the DbContext and have come up with an ambient DbContext, using a
UnitOfWorkScope
which is similar to TransactionScope
.
The main features of the
UnitOfWorkScope
are: - The first method in the call chain opening a
creates an ambient DbContext.UnitOfWorkScope
- Subsequent methods in the call chain utilizes the same DbContext.
- Changes are only saved if all participating scopes called
SaveChanges
- Read only mode is available, where data is read using the existing DbContext, but no changes need to be saved. This is useful for GetSomeThing methods that are used both for pure reading and for reading for update.
A Shared Query
A shared query uses the
UnitOfWorkScope
, instead of creating a DbContext instance directly.
|
The reading purpose is used to mark that this unit of work scope will not do any updates, so
SaveChanges
will not be called. The method can be used both standalone to read data, or to fetch data for subsequent update. Let’s look at using it for updating.
|
Here, the scope is opened for writing. Leaving the scope without calling
SaveChanges
indicates an error, just like
Complete
works on
TransactionScope
Experiences of Using the UnitOfWorkScope
We’re using the unit of work scope in my current project. The experience so far is that we’ve removed the need to pass around DbContext instances all over. It is also much easier to reuse the same common set of base queries for reading and for writing. Any method can easily get hold of the ambient scope. A business logic method on an entity can a service method, which can now be part of the same unit of work without having to pass the DbContext around.
The UnitOfWorkScope code
The code makes use of the
Disposable
base class, that I’ve written about before. The code is also available on GitHub.
|
The code has been updated with a better way to handle when a writing scope is opened to a reading root scope. In this version, an exception is thrown when the child scope is opened. Previously the exception was thrown when the root scope was disposed.
The code is written to be easy to use right and hard to use wrong. There is a risk that someone would call
SaveChanges
directly on the wrapped DbContext – possibly saving changes early. To avoid that there is a guard which disallows that.
The code is thread safe as it uses thread local storage to store the current scope. However it will probably not work with async/await as it does not consider the synchronization context but rather is tied to the thread directly.
from:https://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/