Entity Framework disposing with async controllers in Web api/MVC

后端 未结 2 1692
谎友^
谎友^ 2021-01-04 16:23

I have this little sample of code:

public class ValueController : ApiController
{
    private EstateContext _db;

    public ValueController()
    {
                 


        
相关标签:
2条回答
  • 2021-01-04 17:00

    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.

    0 讨论(0)
  • 2021-01-04 17:19

    You need to create a new instance of your EstateContext inside the async method.

    [HttpPost]
    public async void DoStuff(string id)
    {
        EstateContext db = new EstateContext();
        var entity = await db.Estates.FindAsync(id);
        db.SaveChanges();
    }
    

    However, I believe that if you change the return type of your controller action to Task<ActionResult> then you should be able to reuse the context that is a member of the controller.

    [HttpPost]
    public async Task<ActionResult> DoStuff(string id)
    {
        var entity = await _db.Estates.FindAsync(id);
        _db.SaveChanges();
    }
    
    0 讨论(0)
提交回复
热议问题