Multi-async in Entity Framework 6?

42,277

Solution 1

The exception explains clearly that there is only one asynchronous operation per context allowed at a time.

So, you either have to await them one at a time as the error message suggests:

var banner = await context.Banners.ToListAsync();
var newsGroup = await context.NewsGroups.ToListAsync();

Or you can use multiple contexts:

var banner = context1.Banners.ToListAsync();
var newsGroup = context2.NewsGroups.ToListAsync();
await Task.WhenAll(banner, newsGroup);

Solution 2

If you are using IoC container for your Data Provider injection, consider to use "transient" or "PerWebRequest" type for your lifecycle.

For example: https://github.com/castleproject/Windsor/blob/master/docs/lifestyles.md

Solution 3

If you use Unity for dependency injection with for example repository pattern you will get the following error using two or more contexts with create/update/delete:

The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.

This can be solved using PerRequestLifetimeManager. More info here:

C# EF6 make multiple async calls to one context using Unity - Asp.Net Web Api

container.RegisterType<DbContext>(new PerRequestLifetimeManager());
container.RegisterType<ISupplierRepository, SupplierRepository>();
container.RegisterType<IContactRepository, ContactRepository>();
Share:
42,277
An Hv
Author by

An Hv

Updated on July 08, 2022

Comments

  • An Hv
    An Hv almost 2 years

    This is my code:

    var banner = context.Banners.ToListAsync()
    var newsGroup = context.NewsGroups.ToListAsync()
    await Task.WhenAll(banner, newsGroup);
    

    But when i called the function from controller. It showed error

    A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

    Please help me solve this issue.

  • Pedro.The.Kid
    Pedro.The.Kid almost 10 years
    just a note, if you have a Lazy variable that uses the context in the query even with the await it will throw the same error, just get the property before the query, it was a pain to find out this.
  • Stephen Cleary
    Stephen Cleary almost 10 years
    @Pedro.The.Kid: As a general rule, don't use lazy loading with asynchronous DB access. Lazy loading is always synchronous, so it's far better to use Include or separate queries for the additional data.
  • Zapnologica
    Zapnologica over 8 years
    Is there any specific reason why you need a context per async query? I feel like this becomes quite a bit of a limiting factor.
  • Stephen Cleary
    Stephen Cleary over 8 years
    @Zapnologica: It's just the way ES6 was designed. Each context can only handle one query at a time. So if you finish one query before the next one begins, you'd only need one context. It's only a problem if you want to do multiple queries at the same time.
  • Les
    Les almost 8 years
    @StephenCleary, I am having a hard time finding that query as I there isn't anything immediately before the exception. Is there a way for us to find what is currently being executed? Thanks
  • Les
    Les almost 8 years
    BTW, if I debug, it works! Which means the other thing just happens to have enough time to execute.
  • Stephen Cleary
    Stephen Cleary almost 8 years
    @Bomboca: Not that I know of.
  • Les
    Les almost 8 years
    Got it, I just had to look at what lines executed before even though they were not in the same file but not that hard. Thanks for answering.
  • VivekDev
    VivekDev over 7 years
    Thank You, it works. I have a method call which has two async calls. I had to put await before both of them, and only then did this exception stopped.
  • Ernesto
    Ernesto over 6 years
    But if you cant let them run simultaneously by putting await before each one , is there any significant gain from the async code?
  • Andrew Duffy
    Andrew Duffy over 5 years
    @StephenCleary unable to run multiple queries in parallel with the one db context. Is it just me or is that disappointing?
  • Stephen Cleary
    Stephen Cleary over 5 years
    @AndrewDuffy: It's not just you. :)
  • Jaydeep Shil
    Jaydeep Shil over 3 years
    I am facing the same issue even adding the await while performing db operation. In my scenario I have multiple DbContext and I am spawning 1000 Task using ParallelForEachAsync. Initially I had a single DbContext ,later I have added separate DbContext for two different repository. @StephenCleary your input on my scenario.
  • Stephen Cleary
    Stephen Cleary over 3 years
    @JaydeepShil: If you have 1000 parallel tasks and each of them is doing db work, then you will need 1000 DbContexts for each db.
  • Jaydeep Shil
    Jaydeep Shil over 3 years
    Thank you @StephenCleary for your reply. What should be my design approach here ? I want to spawn 1000 or it can be more items on parallel tasks , each of the task will be performing a read and after that update and then printing those documents in printer. How can I make my dbcontext threadsafe for all parallel operations ? as you also mentioned 1000 DbContext would be required but I have a singleton dbcontext which is injected and this will be a single endpoint call and perform the parallel task in a async scheduler.
  • Stephen Cleary
    Stephen Cleary over 3 years
    @JaydeepShil: You'll need a separate DbContext for each one.
  • Jaydeep Shil
    Jaydeep Shil over 3 years
    @StephenCleary able to solve the issue by adding asynclock before performing async dbcontext operation using SemaphoreSlim. before async get/update I am doing await semaphore.WaitAsync().ConfigureAwait(false); and do the awaited operation and release the semaphore.
  • Stephen Cleary
    Stephen Cleary over 3 years
    @JaydeepShil: OK, but then what's the point of 1000 tasks if they're no longer concurrent?
  • Jaydeep Shil
    Jaydeep Shil over 3 years
    @StephenCleary as dbContext is not thread safe we cant make it concurrent , but to make it concurrent we have to create a new dbcontext for each Task operation , currently our dbcontext is already injected and we are using the injection call from repository , to create a new dbcontext object there will be lot of code changes as there are many flow.
  • Stephen Cleary
    Stephen Cleary over 3 years
    @JaydeepShil: I'm not sure what you want me to say? There's no magic answer that makes this easy. If you want to change from 1 database operation to n concurrent database operations, and if your code uses DbContext, then you will need to use n DbContexts. That will require changes in how your DbContexts are constructed - i.e., a DbContextFactory or just creating new ones as necessary.