Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access DbContext service from background task

Tags:

So ASP.NET Core applications have Dependency Injection built right in. And with Entity Framework Core, you can easily get a scoped DbContext instance from a controller action method.

But this is all limited to controller actions. If you need to start a long-running background task from an action that will communicate with the view in the browser by other means like WebSocket, then you suddenly have nothing at all. The background task can't use the action's DbContext because that was scoped and disposed of when the action returns.

An easy way would be to use what people call a Service Locator. This is a static copy of some IServiceProvider for later access and service resolution. (ASP.NET Core 2.1 might need a different approach as I've read in this comment.) But whereever I look, this is described as anti-pattern. It makes testing harder and obfuscates dependencies. Alright.

So what's the recommended solution for this scenario? I'm somewhere in the middle of nowhere. A background task that might even be started from a scheduler instead of a controller action. No HTTP request anywhere near. What can DI do for me here? Is there a solution without falling back to anti-patterns? I'm sure the creators of ASP.NET Core DI have thought of this.

Is there either a way to get the services resolved there, or change my architecture so that the background task itself comes out of DI somehow?

Update: Requested by a comment, an example: A controller action starts something. This will take a long time, like a network scan. The view returns with something like "Dear user, please wait while you can watch this progress bar." Work continues in the background, continually posting the progress and/or results to the browser. (The browser might also poll progress instead.) The background task needs access to the database to store the scan results. When the scan is finished, the browser can fetch it through another action. So if the background task would just use the controller action's DbContext, that would become unusable as the action has completed.

Another example is a background service that isn't related to a request at all. A service that regularly checks the database and then does something. That also needs a DbContext and has nowhere to even try to steal it.

like image 278
ygoe Avatar asked Jul 28 '18 15:07

ygoe


1 Answers

In that case you can only rely on the servicelocater-pattern which is an anti-pattern. Also the DbContext must be instance-per-dependency (transient) rather than scoped.

My suggestion is to inject IServiceScopeFactory which is a singleton and the beginn a scope in your background-worker that does the deed.

using (var scope = _serviceScopeFactory.CreateScope())
{
   var context = scope.ServiceProvider.GetRequiredService<DbContext>();
   // now do your work
}

There is no other. These things are not part of the mvc pipeline so they cannot be resolved on the fly. I would also suggest not to directly access the dbcontext somewhere. Wrap things in a repository and then use that repository or even wrap that repository in a so called service class. So your logic is encapsulated and your background-worker just executes things when receiving a message.

Not directly related, but ASP.NET-Core has background-worker built-in with IHostedService.

Edit: As Steven suggested below, it might not always be an anti-pattern when manually resolving / initiating classes. At least not when the compositionroot is taking care of it. Here is a link he provided, where it is explained quite good.

like image 90
alsami Avatar answered Oct 17 '22 17:10

alsami