【问题标题】:Why does my IAsyncEnumerable<T> call in EF Core not enumerate asynchronously?为什么我在 EF Core 中的 IAsyncEnumerable<T> 调用不能异步枚举?
【发布时间】:2020-11-17 05:51:04
【问题描述】:

我正在编写一个 C# 方法,它从 SQL 查询(不是直接的DBSet&lt;T&gt;!)流式传输大量行,对它们执行一些转换,然后将结果写入 MongoDB 数据库。我试图让这个运行尽可能快,由于相当高的网络延迟,我想避免多次返回 SQL Server。

我有一个类 StreamlinedCrmTicket,它表示一个 DTO,EF 将原始 SQL 查询的结果投影到该 DTO 上,该查询不接受参数化输入。我正在使用 EF Core 3.1.6 和 .Set&lt;StreamlinedCrmTicket&gt; 技术来执行原始 SQL 查询。然后我使用.AsNoTracking() 来提高性能,因为这只是一个读取操作。最后,我调用.AsAsyncEnumerable(),并将整个shebang 包裹在await foreach 中,而await foreach 又位于标记为async 的方法中。

整个过程是这样的:

await foreach (var ticket in _affinityContext.Set<StreamlinedCrmTicket>().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken))
{
   // Do something with each ticket.
}

我的原始 SQL 查询的源表目前包含大约 120 万行。使用 SSMS 衡量时,有一些连接似乎对查询的执行时间几乎没有变化。

当我执行我的代码时,似乎 EF 启动了查询,但无论它包含什么,foreach 循环的主体都不会开始执行,直到整个查询已执行并从 SQL Server 接收到结果集。这违背了我使用 IAsyncEnumerable 的目的!我的理解是 IAsyncEnumerable 应该允许我在行(或实体)从数据库返回时对其进行操作,而无需等待整个结果集。

一些支持我的理论的想法,即目前这不是异步行为:

  • 一旦对_affinityContext.Set&lt;StreamlinedCrmTicket&gt;().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken) 的调用完成,就会开始大量的网络IO。我可以在我的 Windows 机器上的性能监视器中看到,IO 是与我的代码应该运行的服务器的 SQL Server 连接。
  • 我将foreach 循环体换成了一个非常简单的循环体,它只在网络 IO 停止时运行。
  • 我从 SQL 查询中删除了所有 ORDER BY 子句 - 在此用例中行排序无关紧要,我担心这可能会导致查询需要很长时间才能返回第一行,从而产生错觉同步运行。但是,网络 IO 表明情况并非如此(而且不是 - 我忽略了该子句!)。
  • 如果我在查询中的SELECT 语句中添加TOP 1000,它的执行速度会更快。

我不确定为什么这是同步运行的,而且微软网站上的文档似乎很差!

【问题讨论】:

  • 您确定您的查询首先是can be streamed 吗?
  • 它没有同步运行,它正在异步运行。方法是异步的,是调用this的方法是否被阻塞的问题,还是给了一个指示操作何时完成的任务,而后者在这里发生,所以它是异步的。在计算最终值之前是否提供序列中的较早值完全是一个单独的属性。

标签: c# sql-server .net-core ef-core-3.1 iasyncenumerable


【解决方案1】:

作为参考,GSerg 在 cmets 中建议我的查询可能无法流式传输。在这种情况下,在查询中使用 LEFT OUTER JOIN 导致在 SQL Server 中使用哈希匹配。这会阻止查询结果集流式传输。

【讨论】:

    【解决方案2】:

    您的原始 SQL 不提供用于分页的游标,因此 SQL Server 必须一次性返回整个结果。

    【讨论】:

    • 如果您使用 EF,为什么要编写原始 SQL?
    • 一次性返回结果集是理想的结果,但我希望使用 IAsyncEnumerable 可以让我在从 SQL Server 检索结果集时开始迭代。对原始 SQL 的需求来自于在源数据库中使用绝对异常复杂的查询,我无法将其构建到视图中,也无法在我的 EF 模型中充分表达。不幸的是,该查询使用内联 CASE 语句、基于鉴别器的连接和各种其他有趣的东西。
    • 嗯,你能提供架构和SQL,看看我们是否可以优化查询?
    • 我现在改变了方法;作为视图在数据库中的查询已经返回了我的项目不需要的大量数据,所以我编写了简单的查询来替换视图。不幸的是,架构无法更改(我没有访问权限,并且系统由不支持我们进行修改的第三方维护)所以原始 SQL 是我唯一的选择。
    猜你喜欢
    • 2019-10-24
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 1970-01-01
    • 1970-01-01
    • 2018-10-25
    • 1970-01-01
    • 2010-11-17
    相关资源
    最近更新 更多