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 await
ed immediately instead of potentially included in a Task.WhenAll()
or await
ed 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 await
ing 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.