2

In the past I have used AutoFac to inject a EntityFramework DB context to various services on a InstancePerRequest schedule.

builder.RegisterType<MyDataContext>()
       .As<IDataContext>()
       .As<IUnitOfWork>()
       .InstancePerRequest(); 

This has allowed me to share the context across services when injecting multiple services into a controller.

// Note each of these services take a IDataContext via constructor injection
public FilesController(
            IAnalysisService analysisService,
            IUserService userService)
{

}

I have an action filter that commits my Context at the end of each action request (I am probably looking at changing this but for now it suites my purpose of this question).

public class UnitOfWorkActionAttribute : ActionFilterAttribute
{
    public IUnitOfWork UnitOfWork { get; set; }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        UnitOfWork.SaveChangesAsync().WaitAndUnwrapException();
    }
}

My reading of async in MVC is that the thread that started the request is not guaranteed to finish it due to it being freed up when using await.

In AutoFac injection does this mean that the UnitOfWork instance injected into the ActionFilterAttribute might differ from the one injected into the Controller or is InstancePerRequest not effected by changes to the Thread handling the request?

dreza
  • 3,605
  • 7
  • 44
  • 55
  • It might actually be a pretty bad idea to commit the context at the end of each action request, since "at that point in the application, you simply can't determine for sure that the unit of work should actually be committed". Take a look at [this answer](https://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why) for more info. – Steven Jun 24 '14 at 08:29
  • @steven yes thanks, I think I actually read that SO question during my investigation. I appreciate this is wrong/undesirable and will change it but my actual question still stands. – dreza Jun 24 '14 at 09:26

1 Answers1

1

InstancePerRequest won't be affected by changes to the Thread handling the request, as it does not store anything inside of the Thread. If you read the source of the InstancePerRequest() method you'll see that it's just calling InstancePerMatchingLifetimeScope with a well-known scope tag.

On the other side of things, in the AutoFac.Mvc project, you'll see that in the RequestLifetimeScopeProvider class that it stores the scope in the HttpContext, which is immune to changes in the executing thread.

The only remaining question is: Does your filter execute inside of the scope of the AutoFac filter that starts the request scope? Well, the surest way to find out is to make sure that your UnitOfWork gets injected into your filter. Assuming it does, you can be assured that it is the SAME UnitOfWork that existed throughout the rest of the request.

I would agree with others' suggestions to NOT commit your UnitOfWork (DbContext) in a filter like this. One set of reasons you've already read in the this answer. Another reason is that it implies (to me) that your chosen pattern is to NEVER call SaveChanges() from inside of your individual service methods and instead wait until the end to atomically commit everything. This will work fine until the moment where it doesn't, for example when EF updates or inserts your entities in the wrong order. At this point, you'll have painted yourself into a corner.

I might also presume that you're doing this as a way of avoiding the headaches of explicitly using Transactions, since if you only call SaveChanges() once, all updates will happen in a single, isolated transaction internal to SaveChanges() and you can therefore avoid this pain. This idea of never using explicit transactions in EF, too, is a trap once your app grows to any substantial complexity, though "popularly" championed in various articles around the net. I suggest you plan your infrastructure to accommodate it. Thankfully if you're using something like AutoFac, you can fairly transparently either attach a transaction to your request scope (the all-or-nothing approach) or create a new scope at runtime that contains your transaction, for when you selectively want a transaction.

One possible solution is to call your read-only service calls without a surrounding transaction (to avoid any DB locking) and only use transactions when you know you'll be writing to the database.

Avi Cherry
  • 3,996
  • 1
  • 26
  • 31