.NET Core Web API — How to correctly Fire and Forget Database Commands from Controller end point methods.
Full source code for the examples shown in the post can be found at:
https://github.com/MikeyFriedChicken/WebAPI.FireForgetRepository
Typically a .Net Core Web API project will consist of controller classes to define your request end points and a repository class which contains your database operations. If you are using Entity Framework then the repository will contain a reference to your DBContext which manages the underlying SQL commands and execution.
This works great and typically methods executed from the controller class or your end point all the way to the final database commands all correctly use the async / await keywords. This means that no thread on thread pool will remain blocked. This is important as there is a limited number of threads in the threadpool which are designed to handle many thousands of simultaneous requests. Hence, by using async/await throughout they will never be waiting on blocking calls keeping usage of the threadpool to a minimum.
So what’s the problem? In some instances you may have required database calls in which the main calling request simply do not depend on. Furthermore, such calls may be time consuming so including in the request execution thread would only slow down the total time before a result is returned.
For example logging extra information to an audit table in the database does may not need to be waited on. A typical example shown highlighted below:
In these instances you may want to fire off such a database command on another task or thread and not await the result.
One option might be to create a background service where you can queue the work, however, this in most cases is over kill as using a normal task is completely suitable. So the only problem to solve here is how to instantiate and access the database context correctly on another thread.
So why not just spawn off the task without the await keyword? Well in theory that’s the idea and may seem OK at first thought, however, simply doing this would fail because the injected database context is typically set to a lifetime of the of the request, i.e “scope”. If you were to use the provided database context directly in your newly created task it would no longer be available because most likely the original request has already returned by the time its executed and bad stuff will happen. For example if you were to implement the following INCORRECT code below:
An exception will be throw when accessing the database context inside the repository class. This is because the database context inside the repository is only available to the original request context and not that of the newly created task. Expect this exception to be thrown:
To solve this problem we can use the IServiceScopeFactory which is singleton available by default to create a new scope and through its attached service provider get a newly scoped database context which will be created and cleaned up as part of the newly created task, thus allowing the original request to be returned independently of the new task.
To do this we could simply inject the IServiceScopeFactory into our controller either via the constructor or the end point method, create our scope, resolve a new scoped instance of the repository/database context and use it there and then. For example:
Why not do this? In general we try not to have references and usage of the dependency injector container in our main code, this would be an anti-pattern known as the service locator pattern. In this case although we have to use this anti-pattern somewhere it can be nicely contained and out of sight. This is how the code in the controller should ideally should look as shown in the CORRECT SOLUTION below:
Here we injecting a lambda function into our fire and forget handler which contains a single argument for the repository. This will later be executed on a seperate thread pool thread with a newly created scoped repository and database context passed into our lambda function. No errors and no slowing down our original request and result.
So how do we implement the fire and forget repository handler?
This is quite simple, we just need a single method which creates the task, resolves a newly scoped repository and executes the injected lambda function as shown:
To wire this up simply register the class in configure services like this:
That’s all there is to it! Thankyou.
To copy and paste the code in the screen shots above see the following snippets below, or for the complete source code go to:
https://github.com/MikeyFriedChicken/WebAPI.FireForgetRepository