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

Wac*_*urn 10 c# asp.net asp.net-mvc entity-framework asp.net-web-api

我有这个小代码示例:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Yuv*_*kov 12

但是如果使用async,则此Dispose方法在第一次出现await时调用.

@Konstantins的答案是正确的,但请允许我详细说明为什么会发生这种情况.当你使用一个async void方法时,你基本上是在为你的方法调用创建一个"fire and forget"语义,因为这个方法的任何调用者都不能异步地等待它await,因为它返回void而不是一个等待的形式(例如a Task).

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

当暴露一个Taskor时Task<T>,你明确地告诉调用者"Listen,这个方法是异步的,最终会在将来返回一个值".ASP.NET运行时知道您的控制器尚未完成调用其操作,并等待实际完成操作.

这就是为什么像这样的电话:

[HttpPost]
public async Task DoStuffAsync(string id)
{
    var entity = await _db.Estates.FindAsync(id);
    _db.SaveChanges(); 
}
Run Code Online (Sandbox Code Playgroud)

作品.

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

[HttpPost]
public async Task DoStuffAsync(string id)
{
    using (var db = new EstateContext())
    {
        var entity = await db.Estates.FindAsync(id);
        db.SaveChanges(); 
    }
}
Run Code Online (Sandbox Code Playgroud)

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