实体框架在Web api / MVC中使用异步控制器进行处理

[英]Entity Framework disposing with async controllers in Web api/MVC


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?

每个ApiController / Controller都实现了IDisposable接口。所以在Dispose方法中我想释放任何资源,比如DbContext。但是如果使用async,则此Dispose方法在第一次出现await时调用。所以在等待之后我已经处理了DbContext。那么在使用异步时处理EF上下文的最佳方法是什么?事实证明,在控制器中不可能依赖Dispose方法?

2 个解决方案

#1


10  

But if async is used, this Dispose method calls at first occurrence of await.

但是如果使用async,则此Dispose方法在第一次出现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).

@Konstantins的回答是正确的,但请允许我详细说明为什么会发生这种情况。当你使用异步void方法时,你基本上是在为方法调用创建一个“fire and forget”语义,因为这个方法的任何调用者都不能通过await异步等待它,因为它返回void而不是表单一个等待的(如任务)。

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.

因此,尽管WebAPI确实支持异步方法,但在调用您的操作时,它似乎是一个同步的void返回方法,然后ASP.NET运行时继续处理您的控制器,因为它假定您已完成操作。

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.

当暴露任务或任务 时,您明确告诉调用者“监听,此方法是异步的,最终将返回一个值”。 ASP.NET运行时知道您的控制器尚未完成调用其操作,并等待实际完成操作。

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:

作为旁注 - EF DbContext意味着尽快使用和处理。将它们用作多个动作的全局变量是一个坏主意,因为它们也不是线程安全的。我会建议一个不同的模式,每个动作初始化并处理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.

正如@Wachburn在评论中指出的那样,这种方法确实不太可测试。如果确保在每个操作完成后处理控制器和操作并且不再重复使用上下文,则可以通过DI容器注入DbContext。

#2


4  

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

您需要在async方法中创建EstateContext的新实例。

[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.

但是,我相信如果您将控制器操作的返回类型更改为Task ,那么您应该能够重用作为控制器成员的上下文。

[HttpPost]
public async Task<ActionResult> DoStuff(string id)
{
    var entity = await _db.Estates.FindAsync(id);
    _db.SaveChanges();
}

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:http://www.itdaan.com/blog/2016/03/14/29f66f1e7c4b8cdaec113d7caa91eb74.html



 
© 2014-2018 ITdaan.com 粤ICP备14056181号