【问题标题】:Many nested AggregateExceptions许多嵌套的 AggregateExceptions
【发布时间】:2015-07-25 20:17:52
【问题描述】:

使用 Entity Framework 7,我在一些 linq 上犯了一个简单的错误(使用了 Skip 并忘记包含我的 OrderBy 子句)。

由此引发的异常包括许多嵌套的聚合异常。

生成(并捕获)异常的代码是:

int[] newIds;
try
{
    newIds = await db.Products
        .Where(p => p.PortalId == portalId)
        .Skip(ids.ProductIds.Count) //Skip the rows already read
        .Take(takeTotal) //get the next block
        .Select(p => p.ProductId)
        .ToArrayAsync();
}
catch (AggregateException ex)
{
    Console.WriteLine(ex.Message);
    newIds = new int[] { };
}

上面的代码位于从 Asp.Net 5 WebApi 控制器调用的 repo 类中。所有级别的调用都在使用 async-await。

但是,我从中得到的聚合异常是(从上面显示的 catch 块转储到即时窗口):

System.AggregateException:发生一个或多个错误。 ---> System.AggregateException:发生一个或多个错误。 ---> System.AggregateException:发生一个或多个错误。 ---> System.AggregateException:发生一个或多个错误。 ---> System.AggregateException:发生一个或多个错误。 ---> System.AggregateException:发生一个或多个错误。 ---> System.InvalidOperationException:包含 Skip 运算符的查询 必须包含至少一个 OrderBy 操作。在 Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.GenerateLimitOffset(SelectExpression 选择表达式)在 Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.VisitSelectExpression(SelectExpression 选择表达式)在 Microsoft.Data.Entity.Relational.Query.Expressions.SelectExpression.Accept(ExpressionTreeVisitor 访客)在 Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.GenerateSql(SelectExpression selectExpression, IDictionary`2 parameterValues) 等等等等

这里的实际异常最终被一大堆聚合异常(6 个嵌套层)包裹。我明白为什么我会收到一个汇总异常,但想知道为什么会有这么多异常?更重要的是,因为我正在查看异常之前它冒泡回到控制器入口点。

这会是多层 async-await 的结果(不要认为我有多达 6 层)还是 EF7 实现中的问题?

目前使用的是 EF 7 版本 7.0.0-beta4。

【问题讨论】:

    标签: c# async-await entity-framework-core aggregateexception


    【解决方案1】:

    正如the MSDN page on Task<T> 解释的那样,Task 引发的所有异常都包含在AggregateException 中,然后才被抛出到等待代码。如果您正在使用多个级别的 async/await 并且没有在可能的最低级别捕获此异常,那么每次它冒泡到另一个级别时,它都会再次被包裹,导致 AggregateExceptionAggregateException 内,一个用于每次你都在等待而没有抓到。

    也可能每个操作都算作自己的任务; IE。每次添加另一个操作时,结果都会从前一个操作出来并返回到下一个操作,每个操作都在等待上一个操作。看看:

    newIds = await db.Products               // 1
        .Where(p => p.PortalId == portalId)  // 2
        .Skip(ids.ProductIds.Count)          // 3
        .Take(takeTotal)                     // 4
        .Select(p => p.ProductId)            // 5
        .ToArrayAsync();                     // 6
    

    六层的东西,每一层都在等待上一层的结果。六个AggregateException 层。现在,您的异常是由六个中的第三个引起的,但从错误的性质来看,它很可能来自 EF 在执行任何操作之前读取您的整个查询的部分,并且在这样做时发现您我有一个 .Skip() 没有匹配的 .OrderBy()

    正如 Stephen Cleary 在 cmets 中提醒我的那样,虽然你 await 返回 Task<T> 的东西,它们也会为你做一定程度的展开,所以 await 的行为与 Task<T>.Result 不太一样,这意味着await 应该抛出实际异常而不将其包装在 AggregateException 中。这一切意味着我们最多只有一半的答案(这有点尴尬,因为它已经被接受了)。老实说,我建议你不要接受这个答案,这样其他人就不会跳过你的问题,看看是否有其他人知道可能填补空白的东西。

    【讨论】:

    • 我已经在最低级别抓住了它 - 在它退出 EF 代码的那一刻。这是否意味着 EF 内部有 6 个等待级别?
    • 有可能。也可能是各种链接的 LINQ 运算符都在单独等待......实际上,我将把它添加到我的答案中。
    • 我已经使用控制台应用程序进行了进一步的测试,并且只执行了 EF 代码,并且发生了相同的结果,所以它看起来像我们这样做的 EF 代码的内部结构'我到达了。我将在 Github 项目上提出它 - 这不是一个交易,而是一个可用性。
    • 值得注意的是AggregateException 覆盖GetBaseException() 专门针对这种情况:无论嵌套多深,它都会返回最里面的AggregateException。这使您可以从该异常中获取状态和上下文信息,然后检查其InnerException 是否引发了错误——在本例中为您的InvalidOperationException。只需记住异步代码有可能返回这种异常-matryoshka,并且总是使用GetBaseException() 来跳过所有繁琐的展开。
    • await not 将其异常包装在 AggregateException 中。因此,async/await 层将不会在每个级别添加包装器。此外,LINQ 调用也不添加这些包装器。
    【解决方案2】:

    它与链中调用的方法数量无关。您只需要调用 ToArrayAsync。

    我认为问题出在 Rx.NET 中。我发送了一个 Pull Request 来修复它:https://github.com/aspnet/EntityFramework/issues/2192

    https://github.com/Reactive-Extensions/Rx.NET/pull/131/files

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-25
      • 1970-01-01
      • 2022-10-12
      • 1970-01-01
      • 2018-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多