Search code examples
entity-frameworkasynchronousasync-awaitdispose

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


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?


Solution

  • 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: