5

We are working on a large-ish MVC web application with multiple backing stores including a SQL Server database accessed via Entity Framework 6.0. We are using asynchronous operations wherever we can, but we repeatedly--in initial implementations--come across scenarios where we inadvertently start a second async operation on the same EF context (we use the same context for all EF operations), which throws an exception.

I am not new to C# enterprise app development, but I am pretty new to EF and I've been thinking about possible ways to communicate that a given repo method must be awaited immediately instead of potentially included in a Task.WhenAll() or awaited after another method. Has anyone considered this?

Some pseudo-code for discussion purposes:

// we know that our CURRENT implementation of ICarRepo will utilize EF
// this declaration is in a backend-agnostic library
public interface ICarRepo
{
  Task<ICar> GetAsync(int carID);
}

// in our EF project
public class CarRepoEF: ICarRepo
{
   public async Task<ICar> GetAsync(int carID)
   {
     return await DataContext.Cars.SingleOrDefaultAsync(c => c.ID == carID);
   }
}

Our current approach is to simply provide comments in scenarios where we can fire off an async repo method and continue before awaiting the result:

// not EF
var driverTask = driverRepo.GetAsync(driverID);
//...other repo methods, logic, object instantiation, etc
// EF, await it immediately
var car = await carRepo.GetAsync(carID);
var driver = await driverTask;

We are using DI everywhere (including the DataContext in a request scope) so if we wanted to communicate this to consumers we'd have to do so through the backend-agnostic interface, which makes me think this is perhaps a fool's errand (why make the repo interface communicate a limitation of the current implementation of the interface?). Specifically, I was considering wrapping Task in a custom class that would communicate that it can't handle more than one at a time (singleton???), or extending Task itself to communicate the same and have interfaces that front EF operations return these instead of a plain Task.

Should I give up on trying to engineer a solution to this quasi-problem? Is there a more fundamental, architectural change that could make this problem go away that I am not aware of?

EDIT

After some more research, I did find a SO question that deals with this issue (even using the same DI framework) and one of the answers introduced the idea of using a factory to create contexts.

8
  • 2
    if sharing the DataContext means that you can't really claim to be a Task, either don't share the DataContext or don't claim to be a Task
    – Caleth
    Commented Oct 7, 2015 at 13:11
  • @Caleth that is an excellent, succinct way of putting it and makes me wonder why there aren't more discussions about this issue with EF (or maybe everybody is creating contexts for each async request). Commented Oct 7, 2015 at 13:36
  • @SvenGrosen There is no need for discussion. Context is not thread-safe and it is stressed enough that it should only ever be accessed from single thread/continuation chain.
    – Euphoric
    Commented Oct 7, 2015 at 13:40
  • @Euphoric I meant discussion around ways to work around this limitation, which I certainly understand why it is in place. Again, I'm an EF newbie so my whole line of thinking is probably naive. Commented Oct 7, 2015 at 13:43
  • @SvenGrosen The "workaround" is to use Context in single thread only. EF is not going to change just because your code is massive spaghetti and it is impossible to know from which thread the Context will be called.
    – Euphoric
    Commented Oct 7, 2015 at 13:45

1 Answer 1

1

For your case, you could introduce a factory class that would be responsible for creating DbContext instances

so that

IDbContextFactory dbContextFactory = new DbContextFactory(); // <-- this factory knows how to create dbContexts on demand

MyDbContext dbContext = dbContextFactory.NewDbContext() // <-- request a "fresh" dbContext on demand

Depending on the requirement, inject a IDbContextFactory when you need multiple DbContexts or just DbContext if only one DbContext is enough.

2
  • Note also that a factory is not the only way to achieve individual contexts. You can also inject the context itself but set your DI registration to be transient (i.e. every injection is a new instance) or scoped (if you manage your scopes to that degree)
    – Flater
    Commented Sep 17, 2023 at 23:14
  • @Flater Depending on the D.I. configuration for the way to use a dependency make the behavior less discoverable IMHO. That's why i'd rather have a factory than depending on transient as it's less obvious reading at the code at hand.
    – Saï
    Commented Nov 23, 2023 at 11:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.