【问题标题】:ObjectDisposedException when returning AsAsyncEnumerable()返回 AsAsyncEnumerable() 时的 ObjectDisposedException
【发布时间】:2020-06-28 19:49:51
【问题描述】:

在我的 .NET Core 3 WebAPI 项目中,我有以下简单的方法调用:

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList() {
    using var db = new MpaContext();

    return db.ViewerRoles.AsAsyncEnumerable();
}

这会引发 ObjectDisposedException。 AsAsyncEnumerable() 相对较新,我找不到任何合适的例子来说明如何在这种情况下使用它。我是否应该删除 using 关键字并且实体框架数据库连接会神奇地自行处理?还是有什么妙招?

【问题讨论】:

    标签: c# entity-framework asp.net-web-api .net-core async-await


    【解决方案1】:

    你有两个选择。在您的GetViewList 方法中枚举IAsyncEnumerable

    [HttpGet("ViewerRoles")]
    public async IAsyncEnumerable<ViewerRole> GetViewList()
    {
        using var db = new MpaContext();
        await foreach (var item in db.ViewerRoles.AsAsyncEnumerable().ConfigureAwait(false))
        {
            yield return item;
        }
    }
    

    ...或安装System.Interactive.Async 包并使用静态AsyncEnumerableEx.Using 方法:

    [HttpGet("ViewerRoles")]
    public IAsyncEnumerable<ViewerRole> GetViewList()
    {
        return AsyncEnumerableEx.Using(() => new MpaContext(),
            db => db.ViewerRoles.AsAsyncEnumerable());
    }
    

    这是AsyncEnumerableEx.Using方法的签名:

    public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
        Func<TResource> resourceFactory,
        Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
        where TResource : IDisposable;
    

    不幸的是,这个库似乎没有可用的在线文档。

    【讨论】:

    • 您个人更喜欢哪个选项? Jonas Hoegh 还发布了另一个想法。
    • @AndréReichelt 这两个选项在功能上是等效的,并且同样有效。所以这是一个品味问题。关于 Jonas Høgh 的建议,我的知识不足以判断。
    【解决方案2】:

    你应该在你的控制器上实现IDisposable,并在控制器的Dispose方法中处理DbContext

    【讨论】:

    • 与 Theodor Zoulias 的想法相比,您更喜欢哪一个?
    【解决方案3】:

    using 语句在这种情况下将您的 dbContext 范围限定为函数范围,因此正确的方法是在从操作返回之前进行枚举,否则您返回的内容稍后无法正确评估(在函数返回和上下文处理之后)

    或者,您可以将 dbContext 创建移动到控制器的范围,这可以是请求范围(通过框架的 DI 实现并不是那么困难,DI 将使用 IDisposable 处理所有事情,并以“神奇”的方式请求)

    【讨论】:

      【解决方案4】:

      我还找到了使用方法Response.RegisterForDispose() 的方法。但我仍然不知道哪种方法最有前途。

      [HttpGet("ViewerRoles")]
      public IAsyncEnumerable<ViewerRole> GetViewList() {
          MpaContext db = new MpaContext();
      
          Response.RegisterForDispose(db);
      
          return db.ViewerRoles.AsAsyncEnumerable();
      }
      
      

      【讨论】:

        【解决方案5】:

        我看到所有答案都有其自身的意义,而且都是正确的。所以,我只能对它们做一些澄清。

        您应该根据暴露对象的方式选择处理对象的时刻。例如,您的原始代码隐含地将MpaContext db 暴露给AspNet 管道,并且在netcore 完成他的工作之前,您不能处置db。因此,正如您所提到的,您可以通过Response.RegisterForDispose() 注册处置它。但是,这并不常见,因为您通常无法访问 Response - 您只能在 Controller 中执行此操作,或者与 Controller 依赖项共享它,但这会增加代码复杂性。

        这就是为什么您可以通过依赖控制器的生命周期来避免这种情况。因为,它在请求范围内,它将一直存在,直到发送响应。因此,您可以创建 db 作为控制器依赖项并将其保存在属性中。你也应该在控制器上实现IDisposable

        public class RoleController : IDisposable
        {
            private MpaContext DbContext { get; }
        
            public RoleController()
            {
                DbContext = new MpaContext();
            }
        
            [HttpGet( "ViewerRoles" )]
            public IAsyncEnumerable<ViewerRole> GetViewList()
            {
                return DbContext.ViewerRoles.AsAsyncEnumerable();
            }
        
            public void Dispose()
            {
                DbContext.Dispose();
            }
        }
        

        在这种情况下,即使您将逻辑移至其他类(我相信它应该如此),您也可以坚持这种模式。但是,如果您手动创建一次性对象,您应该关心处理它们。这就是 DI 提供帮助的时刻。

        通过使用 DI,您可以忘记处置由 DI 创建的对象。 DI 将在其生命周期结束时对任何依赖项调用 Dispose()。如果您使用MpaContext 下的EntityFramework,请调用AddDbContextPool&lt;MpaContext&gt;()AddDbContext&lt;MpaContext&gt;() 注册您的MpaContext。通过这种方法,您将获得控制器的清晰代码。

        public class RoleController
        {
            private MpaContext DbContext { get; }
        
            public RoleController( MpaContext dbContext )
            {
                DbContext = dbContext;
            }
        
            [HttpGet( "ViewerRoles" )]
            public IAsyncEnumerable<ViewerRole> GetViewList()
            {
                return DbContext.ViewerRoles.AsAsyncEnumerable();
            }
        }
        

        如果您不想将MpaContext 暴露给控制器并想在GetViewList() 中手动创建它,您仍然可以在操作方法中枚举结果并按照Theodor Zoulias 的回答处理上下文。但是,如果您可以简单地将这项工作委托给 DI,您为什么要这样做。

        【讨论】:

        • 那么如果我使用依赖注入,对象会被自动释放?
        • @AndréReichelt 是的,这是真的。有关更多信息,您可以参考此docs.microsoft。请参阅范围验证服务处置部分。
        • 另外,你会发现这篇文章很有帮助docs.microsoft
        • 非常感谢!
        猜你喜欢
        • 2019-10-04
        • 2012-08-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多