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.

Sven Grosen
  • 151
  • 5
  • 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 Oct 07 '15 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). – Sven Grosen Oct 07 '15 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 Oct 07 '15 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. – Sven Grosen Oct 07 '15 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 Oct 07 '15 at 13:45
  • @Euphoric the intent of my post is not to complain about how EF contexts work, but to ask for suggestions on how to communicate that a given subset of repo operations are bound to EF and therefore cannot be done in parallel when using the same context. I'm new on this project and unfamiliar with EF conventions, so it is entirely possible that the established organization is "spaghetti", but I'm inclined to think not. – Sven Grosen Oct 07 '15 at 13:56
  • Your problem is lower level than you realise. You shouldn't even be using a repository pattern with Entity Framework, as [it is already a repository](https://softwareengineering.stackexchange.com/a/220126/289765). – Turksarama Jan 22 '23 at 12:37

1 Answers1

0

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.

Saï
  • 1
  • 1