I have this little sample of code:
public class ValueController : ApiController
{
private EstateContext _db;
public ValueController()
{
_db = new EstateContext();
}
[HttpPost]
public async void DoStuff(string id)
{
var entity = await _db.Estates.FindAsync(id); //now our method goes out and Dispose method is calling
//returns here after disposing
_db.SaveChanges(); // _db is disposed
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_db.Dispose();
}
}
Every ApiController/Controller implements IDisposable interface. So in the Dispose method I want to free up any resources such as DbContext. But if async is used, this Dispose method calls at first occurrence of await. So after await I have DbContext already disposed. So what is the best way to dispose EF Contexts when async is used? It turns out that it is not possible to rely on Dispose method in controller?
But if async is used, this Dispose method calls at first occurrence of await.
@Konstantins answer is correct, but allow me to elaborate a bit on why that happens. When you use an async void
method, you're basically creating a "fire and forget" semantics to your method call, because any caller of this method can't itself asynchronously wait on it with await
, as it returns void
and not a form of an awaitable (such as a Task
).
Thus, although WebAPI does support asynchronous methods, when invoking your action it seems as if it was a synchronous void
returning method, and then the ASP.NET runtime goes on to dispose your controller, because it assumes that you're done with the action.
When exposing a Task
or Task<T>
, you're explicitly telling the caller "Listen, this method is asynchronous an will eventually return a value in the future". The ASP.NET runtime knows your controller hasn't finished invoking his action, and awaits upon the actual completion of the action.
This is why a call like this:
[HttpPost]
public async Task DoStuffAsync(string id)
{
var entity = await _db.Estates.FindAsync(id);
_db.SaveChanges();
}
Works.
As a side note - EF DbContext
are meant to be used and disposed as soon as possible. Using them as a global variable for multiple actions is a bad idea, as they are not thread-safe either. I would suggest a different pattern where each action initializes and disposes the DbContext
:
[HttpPost]
public async Task DoStuffAsync(string id)
{
using (var db = new EstateContext())
{
var entity = await db.Estates.FindAsync(id);
db.SaveChanges();
}
}
As pointed out by @Wachburn in the comments, this approach is indeed less testable. If you ensure that your controller and action are disposed after each action is complete and there's no re-use of the context, you're safe to inject the DbContext
via a DI container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With