How to handle async metod with EF operations and using await before there

45 Views Asked by At

I've got controller like below, api is called from client

 [HttpPost("UpdateSomething")]
        public IActionResult UpdateSomething([FromBody] UpdateSomethingRequestDTO request)
        {
            someService.UpdateUpdateSomething(request);
            return Ok();
        }

Than inside UpdateUpdateSomething I want to make some EF operations to update something but than I have to run background process in :

(dbContext is injected)

public void UpdateUpdateSomething(UpdateUpdateSomethingRequestDTO request)
        {
 var something = dbContext.Something
                .Where(s => ... bla bla);

something.someField = "someValue";
  dbContext.Something.Update(something );
                dbContext.SaveChanges();
                dbContext.Entry(something ).State = EntityState.Detached;

//finally after that operation I need to call asynch method
BackgroundOperation();
}
BackgroundOperation(){
var somethings= (from f in dbContext.Something.AsNoTracking()
//dbContext is still ok not disposed yet..

  foreach (Something something in somethings)
   {
         var response = await _httpClient.GetAsync(url);

         something.someField2 = response.text;
         //dbContext was disposed automatically because that await

         dbContext.Entry(Something).State = EntityState.Modified;
         dbContext.SaveChanges();
         dbContext.Entry(Something).State = EntityState.Detached;
   }
}

I know if I will call

await  someService.UpdateUpdateSomething(request);

dbContext won't be disposed but I want to run it in background..

I know I can do reinitialize dbContext but this is something that would be nice to avoid

using dbContext? dbContext = new();

Separate call just for UpdateUpdateSomething making controller for it etc could resolve issue But I need to run it in same controller.

someService.UpdateUpdateSomething(request);

I cannot see any other ways how to handle that. But maybe you've got some other ideas?

2

There are 2 best solutions below

0
Guru Stron On BEST ANSWER

There is not much what you can do here without reworking your approach, this is how DI works, all disposable components created by the scope will be disposed when the scope ends. From the docs:

Injection of the service into the constructor of the class where it's used. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

Note that instances created by factory-like approaches are not (usually) controlled/disposed by the container (for example like EF Cores DbContext factory) and should be disposed "manually".

So the problem here is that your context is registered like scoped (default behavior, no difference if it is transient), scope will be created by ASP.NET Core infrastructure on per-request basis and will be finished after the request is handled leading to the context disposal, so your "background" work will highly likely used the already disposed instance (i.e. behavior you observe).

What you can do:

I know I can do reinitialize dbContext but this is something that would be nice to avoid

Basically most solutions would require something similar:

Note that your approach with BackgroundOperation is basically fire-and-forget which arguably should be discouraged (like unobserved exceptions, or "premature" disposal of resources as you observe), I would highly recommend to switch from it to using some other background scheduling option, either something like Hangfire or Quartz.NET or if this is considered too heavyweight - just use the hosted services provided by ASP.NET Core (see the Background tasks with hosted services in ASP.NET Core)

See also:

2
Rakesh Kamath On

You need use Async Methods on dbcontext like FindAysnc()/SaveChangesAsync() to work in asynchronously.

//Controller
[HttpPut]
[Route("{id}")]
public async Task<IActionResult> Update([FromRoute] int id, [FromBody] YourDto yourDto)
{
    var postModel = await _postsService.UpdateAsync(id, yourDto);
    if (postModel == null)
    {
        return NotFound();
    }
    return Ok();

}
 //Implementation
 public async Task<YourEntity?> UpdateAsync(int id, YourDto yourData)
 {
      var postModel = await _context.YourEntity.FindAsync(id); 
      //update data here...       
      await _context.SaveChangesAsync();    
      return data;
 }