【问题标题】:Entity Framework disposing with async controllers in Web api/MVC在 Web api/MVC 中使用异步控制器处理的实体框架
【发布时间】:2016-06-29 12:34:27
【问题描述】:

我有这个小代码示例:

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();
    }
}

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

【问题讨论】:

  • async void?这似乎是个坏主意……
  • @David 你为我节省了很多时间))谢谢

标签: c# asp.net asp.net-mvc entity-framework asp.net-web-api


【解决方案1】:

但是如果使用异步,这个 Dispose 方法在第一次出现时调用 等待。

@Konstantins answer 是正确的,但请允许我详细说明为什么会发生这种情况。当您使用async void 方法时,您基本上是在为您的方法调用创建“即发即弃”语义,因为此方法的任何调用者本身都不能使用await 异步等待它,因为它返回@987654324 @ 而不是可等待的形式(例如 Task)。

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

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

这就是为什么这样的调用:

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

工作。

附带说明 - EF DbContext 旨在尽快使用和处置。将它们用作多个操作的全局变量是个坏主意,因为它们也不是线程安全的。我会建议一种不同的模式,其中每个操作都会初始化和处理DbContext

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

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

【讨论】:

  • 感谢您的详细解释。但在第二部分——注入和单元测试呢?如果我无法设置上下文,如何测试这样的控制器和操作?
  • @Wachburn 你是对的,它的可测试性较低。如果您确保在每次方法调用之后释放您的控制器并且在操作之间没有重复使用相同的上下文,那么您可以安全地使用使用 DI 容器注入正确依赖项的方法。
  • 我的主要方法不是一个动作,返回类型不是 void 而是任务,但是 DB 上下文仍在处理.....但是我在 catch 语句中使用 DBContext,可以是这个原因吗?
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 2016-08-02
    相关资源
    最近更新 更多