Make the DbContext Ambient with UnitOfWorkScope

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 UnitOfWorkScope creates an ambient DbContext.
  • 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.

public static Entities.Car GetCar(int id)
{
    using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Reading))
    {
        return uow.DbContext.Cars.Single(c => c.CarId == id);
    }
}

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.

using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Writing))
{
    Car c = SharedQueries.GetCar(carId);
    c.Color = "White";
    uow.SaveChanges();
}

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.

/// <summary>
/// Purpose of a UnitOfWorkScope.
/// </summary>
public enum UnitOfWorkScopePurpose
{
    /// <summary>
    /// This unit of work scope will only be used for reading.
    /// </summary>
    Reading,
 
    /// <summary>
    /// This unit of work scope will be used for writing. If SaveChanges
    /// isn't called, it cancels the entire unit of work.
    /// </summary>
    Writing
}
 
/// <summary>
/// Scoped unit of work, that merges with any existing scoped unit of work
/// activated by a previous function in the call chain.
/// </summary>
/// <typeparam name="TDbContext">The type of the DbContext</typeparam>
public class UnitOfWorkScope<TDbContext> : Disposable
    where TDbContext : DbContext, new()
{
    /// <summary>
    /// Handle class for holding the real DbContext and some state for it.
    /// </summary>
    private class ScopedDbContext : Disposable
    {
        /// <summary>
        /// The real DbContext.
        /// </summary>
        public TDbContext DbContext { get; private set; }
 
        /// <summary>
        /// Has there been a failure that should block saving?
        /// </summary>
        public bool BlockSave { get; set; }
 
        /// <summary>
        /// Was any unit of work scope using this DbContext opened for writing?
        /// </summary>
        public bool ForWriting { get; private set; }
 
        /// <summary>
        /// Switch off guard for direct calls to SaveChanges.
        /// </summary>
        public bool AllowSaving { get; set; }
 
        /// <summary>
        /// Ctor.
        /// </summary>
        /// <param name="forWriting">Is the root context opened for writing?</param>
        public ScopedDbContext(bool forWriting)
        {
            ForWriting = forWriting;
            DbContext = new TDbContext();
            ((IObjectContextAdapter)DbContext).ObjectContext.SavingChanges
                += GuardAgainstDirectSaves;
        }
 
        void GuardAgainstDirectSaves(object sender, EventArgs e)
        {
            if (!AllowSaving)
            {
                throw new InvalidOperationException(
                    "Don't call SaveChanges directly on a context owned by a UnitOfWorkScope. " +
                    "use UnitOfWorkScope.SaveChanges instead.");
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (DbContext != null)
                {
                    ((IObjectContextAdapter)DbContext).ObjectContext.SavingChanges
                        -= GuardAgainstDirectSaves;
 
                    DbContext.Dispose();
                    DbContext = null;
                }
            }
            base.Dispose(disposing);
        }
    }
 
    [ThreadStatic]
    private static ScopedDbContext scopedDbContext;
 
    private bool isRoot = false;
 
    private bool saveChangesCalled = false;
 
    /// <summary>
    /// Access the ambient DbContext that this unit of work uses.
    /// </summary>
    public TDbContext DbContext
    {
        get
        {
            return scopedDbContext.DbContext;
        }
    }
 
    private UnitOfWorkScopePurpose purpose;
 
    /// <summary>
    /// Ctor
    /// </summary>
    /// <param name="purpose">Will this unit of work scope be used for reading or writing?</param>
    public UnitOfWorkScope(UnitOfWorkScopePurpose purpose)
    {
        this.purpose = purpose;
        if (scopedDbContext == null)
        {
            scopedDbContext = new ScopedDbContext(purpose == UnitOfWorkScopePurpose.Writing);
            isRoot = true;
        }
        if (purpose == UnitOfWorkScopePurpose.Writing && !scopedDbContext.ForWriting)
        {
            throw new InvalidOperationException(
                "Can't open a child UnitOfWorkScope for writing when the root scope " +
                "is opened for reading.");
        }
    }
 
    /// <summary>
    /// Dispose implementation, checking post conditions for purpose and saving.
    /// </summary>
    /// <param name="disposing">Are we disposing?</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // We're disposing and SaveChanges wasn't called. That usually
            // means we're exiting the scope with an exception. Block saves
            // of the entire unit of work.
            if (purpose == UnitOfWorkScopePurpose.Writing && !saveChangesCalled)
            {
                scopedDbContext.BlockSave = true;
                // Don't throw here - it would mask original exception when exiting
                // a using block.
            }
 
            if (scopedDbContext != null && isRoot)
            {
                scopedDbContext.Dispose();
                scopedDbContext = null;
            }
        }
 
        base.Dispose(disposing);
    }
 
    /// <summary>
    /// For child unit of work scopes: Mark for saving. For the root: Do actually save.
    /// </summary>
    public void SaveChanges()
    {
        if (purpose != UnitOfWorkScopePurpose.Writing)
        {
            throw new InvalidOperationException(
                "Can't save changes on a UnitOfWorkScope with Reading purpose.");
        }
 
        if (scopedDbContext.BlockSave)
        {
            throw new InvalidOperationException(
                "Saving of changes is blocked for this unit of work scope. An enclosed " +
                "scope was disposed without calling SaveChanges.");
        }
 
        saveChangesCalled = true;
 
        if (!isRoot)
        {
            return;
        }
 
        scopedDbContext.AllowSaving = true;
        scopedDbContext.DbContext.SaveChanges();
        scopedDbContext.AllowSaving = false;
    }
}

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.

  • withnailandi on 2013-01-19

    of course this will not be be compatible with methods returning IQueryable … also can’t you accomplish this with DI/IoC ?

    • Anders Abel on 2013-01-19

      We’ve used this extensively with methods returning IQueryable. That is where the approach really shines, because the calling method is in complete control of whether the DbContext is to be kept open or not. In an MVC application we usually open the UnitOfWorkScope in the controller method to make sure we have one unit of work across all operations.

      My own experience with Dependency Injection containers is very limited (it is an area I really have to look more into). Do you know of a DI container that allows for the fine grained control of lifetime and the “voting system” for SavingChanges that the UnitOfWorkScope provides?

      • withnailandi on 2013-01-19

        ok so you have to wrap calls to methods in other classes that return IQueryable in another using statement (given that the method is already wrapping the context) even though you don’t directly use the scope variable? Doesn’t seem right but I’m not sure there’s a better solution (maybe this http://stackoverflow.com/a/10588594/817284 ?) or maybe the whole premise is flawed in reality and is becoming a time hole for a lot of people …

        Re DI I was hoping you would know actually ; ) If DI can solve this then that would be the way to go .. you can do it without thinking about when you save :P http://stackoverflow.com/a/6350773/817284

  • Danilo on 2013-09-19

    Hi,
    there is a small error in public UnitOfWorkScope(UnitOfWorkScopePurpose purpose), I think you miss to include the throw new InvalidOperationException( above the text:

    “Can’t open a child UnitOfWorkScope for writing when the root scope ” +
    “is opened for reading.”);

    Anyway thanks for sharing it, I really appreciate your code :)
    Keep up the good work
    Regards,
    D.

    • Anders Abel on 2013-09-19

      Thanks for noticing, I’ve fixed the post.

  • Danilo on 2013-09-27

    Hi again :), is there any reason why you check that a writing scope cannot be contained in a reading scope? This limit gives me the feel I need to keep my transaction open longer than I need. On the other side closing it, cause me issues with the Attach/Detach of the entities.
    Every help is greatly appreciated.

    Watching the implementation I think the problem is to determine the root scope, which in my case I would like to be the relative writing root.

    I made a simple mod to your class to achieve what I wanted:

    // Added declaration in UnitOfWorkScope:  
    [ThreadStatic]  
    private static Stack<UnitOfWorkScope> previousScopes;
     
    // Replaced the check in the constructor with following:  
    if (previousScopes == null) 
        previousScopes = new Stack<UnitOfWorkScope<TDbContext>>();
     
    if (purpose == UnitOfWorkScopePurpose.Writing) 
        previousScopes.Push(this);
     
    // Added in Dispose:  
    if (purpose == UnitOfWorkScopePurpose.Writing)  
    previousScopes.Pop();
     
    // And finally changed the check to inhibit SaveChanges  
    if (previousScopes.Count != 1)  
    return;

    • Anders Abel on 2013-09-30

      The limit that the root scope must be opened for writing if any child scopes are opened was set because it was the most simple thing for me at the time. My most common use case is that some method deep down in the call stack reads some data from the DB without knowing if it is just a read or if data might be written back. The top method in the call stack, that opens the root scope could either be a read only method or an update that saves back.

      There is also another problem that must be solved if child scopes are allowed to be opened for writing with the root being a read scope.

       
      using(var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Reading))
      {
        SomeWritingMethod();
        SomeOtherWritingMethod();
      }

      If SomeWritingMethod and SomeOtherWritingMethod both open a scope for writing and calls SaveChanges we would end up saving the changes twice in one unit of work which spoils the entire intention of the unit of work pattern. Alternatively SaveChanges would be mandatory to call on root scopes, which would be a waste of resources on reading.

      • Fabio Angela on 2013-11-27

        what about using a “reference counter” to keep a track of “writing request” decrementing it when inner scope call savechanges?

      • Anders Abel on 2013-11-27

        In the example above, the counter would be zero after the call to SomeWritingMethod, but it would be incorrect to save the changes at that point as the changes of SomeOtherWritingMethod are inside the same scope.

      • Fabio Angela on 2013-11-28

        yeah but if you change the way you check and call SaveChanges on DBContext, you can do all the saving logic check in the root UnitOfWorkScope and if in inner scope is legit to call SaveChanges, just set a flag like “HaveToCallSaveChanges” on the root UnitOfWorkScope (you need of course a reference on the inner or an event or whatever to set that flag) so that when the root is closed, you can call SaveChanges even if the root is a UnitOfWorkScopePurpose.Reading (you can demand it to the Dispose function maybe)

        don’t know if i’ve explained myself, i meant something like, when you call :

        saveChangesCalled = true

        you set a flag on the root UnitOfWorkScope (thus you need a reference to it) like

        UnitOfWorkScopeRoot.HaveToCallSaveChanges = true

        This way, inside your check, in Dispose

        if (scopedDbContext != null && isRoot)

        you can add this check (since you are at root level)

        if (this.HaveToCallSaveChanges){
          scopedDbContext.DbContext.SaveChanges();
        }

        finally, in SaveChanges function you can check if it is called at root level and set this.HaveToCallSaveChanges=false

        with those mods, you can have a root unitofscope of read type that can have inner write scopes

        my2cents

      • Anders Abel on 2013-11-28

        I didn’t think that far into it. You’re right, that approach should work.

        The only (small) issue is that there will be no way to detect if the outermost reading-scope is exited because of an exception or normal flow. But on the other hand, if all writing-scopes have been marked as saved then it should be okay with a save.

  • Federico Bergstein on 2014-01-19

    Is it really safe to use [ThreadStatic]? for example in an ASP.Net enviroment a request can start in one thread an finish in another, this is called Thread Agility.

    • Anders Abel on 2014-01-19

      No, [ThreadStatic] combined with thread agility is not a good idea. Neither will it work correctly with async code.

      The applications that I use this functionality in are ASP.NET MVC applications, with plain old synchronous controllers and DB queries. The UnitOfWorkScope is often opened in the controller so it only lives for the duration of code I’ve written myself – where I can control and make sure that no async code is used.

      I’ve thought of making another implementation that would work with async code as well, but haven’t quite got my head around how to do that with execution and synchronization contexts. If you know a way, please tell me!

  • Federico Bergstein on 2014-01-20

    Storing ScopedDbContext in HttpContext.Current.Items instead of making it Thread Static could work, but only in an ASP.NEt Environment, something more abstract could be invented, I think, perhaps say an “IPersistValue” interface and IOC? where “IPersistValue” would be responsible of storing the ScopedDbContext somehow (HttpContext in a web site, OperationContext in WCF, etc)

  • Sjoerd Westerhof on 2014-04-08

    So Anders, first of all i wanted to say i really like your solution.
    I just wanted to mention that from what i understand, Threadstatic is not a good solution for ASP.net applications. See this post:
    http://piers7.blogspot.nl/2005/11/threadstatic-callcontext-and_02.html
    or is this maybe different for MVC applications?

    • Anders Abel on 2014-04-09

      Thanks for linking to that post. It clearly shows that the page life cycle model of ASP.NET WebForms is not compatible with using ThreadStatic the way I do in this solution.

      When using ASP.NET MVC it is not a problem though, as MVC doesn’t have the page life cycle. A single thread is used for executing all code from the controller (unless async/await is used, which is another incompatibility of this solution).

      • Sjoerd Westerhof on 2014-04-10

        Hi Anders, thanks for the reply

        The page i linked to does show a WebForms example but it also points out threadsstatic has other issues that could be relevant to MVC like explicit cleaning up or resources and Threads handling multiple requests in consecutive, thus maybe your AmbientContext outlives the HttpRequest and is shared over multiple requests?

        Again, i’m really like your solution so i’m not trying to nitpick here. Just want to verify these concerns with you.

      • Anders Abel on 2014-04-10

        The cleanup is an important part for MVC apps as well and that’s handled in the Dispose() method where it explicitly sets the scopedDbContext to null if the disposed UnitOfWorkScope is the root (that is, the outermost) scope. As long as the UnitOfWorkScope is used inside a using-block as intended it will clean up properly.

  • Sjoerd Westerhof on 2014-04-10

    Of course, you are right!
    Because of the using statement, dispose will always be called, i hadn’t realised, and because of the implicit finally of the using, even is the case of an exception, dispose will be executed.

    So overall, nice and for webforms implementation, use something like httpContext.Current.Items like Frederico suggested

Software Development is a Job – Coding is a Passion

I'm Anders Abel, a systems architect and developer working for Kentor in Stockholm, Sweden.

profile for Anders Abel at Stack Overflow, Q&A for professional and enthusiast programmers

The complete code for all posts is available on GitHub.

Popular Posts

Archives

Series

Powered by WordPress with the Passion for Coding theme.