为什么我在 EF Core 中调用 IAsyncEnumerable<T> 不异步枚举?
Why does my IAsyncEnumerable<T> call in EF Core not enumerate asynchronously?
我正在编写一个 C# 方法,该方法从 SQL 查询(不是直接的 DBSet<T>
!)流式传输大量行,对它们执行一些转换,然后将结果写入MongoDB 数据库。我正在尝试尽快完成此 运行,并且由于相当高的网络延迟,我想避免多次 return 访问 SQL 服务器。
我有一个 class、StreamlinedCrmTicket
,它表示一个 DTO,EF 将原始 SQL 查询的结果投射到该 DTO 上,该查询不采用参数化输入。我正在使用 EF Core 3.1.6 和 .Set<StreamlinedCrmTicket>
技术来执行原始 SQL 查询。然后我使用 .AsNoTracking()
来提高性能,因为这只是一个读取操作。最后,我调用 .AsAsyncEnumerable()
,并将整个 shebang 包装在 await foreach
中,而后者又位于标记为 async
.
的方法中
整个事情看起来像这样:
await foreach (var ticket in _affinityContext.Set<StreamlinedCrmTicket>().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken))
{
// Do something with each ticket.
}
我的原始 SQL 查询的来源 table 当前包含大约 120 万行。使用 SSMS 测量时,有少数联接似乎对查询的执行时间造成的变化很小。
当我执行我的代码时,似乎 EF 启动了查询,但是 foreach 循环的主体,无论它包含什么,直到整个查询执行完毕并且从 SQL服务器。这违背了我使用 IAsyncEnumerable 的目的!我的理解是 IAsyncEnumerable 应该允许我对行(或实体)进行操作,因为它们来自数据库 return,而不必等待整个结果集。
一些支持我的理论的观点是:
- 对
_affinityContext.Set<StreamlinedCrmTicket>().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken)
的调用一结束,大量的网络IO就开始了。我可以在我的 Windows 机器上的性能监视器中看到 IO 是一个 SQL 服务器连接到服务器我的代码应该 运行ning 反对。
- 我将
foreach
循环的主体交换为一个非常简单的循环,一旦网络 IO 停止,它只需要 运行s。
- 我从 SQL 查询中删除了所有
ORDER BY
子句 - 行排序在此用例中无关紧要,我担心这可能会导致查询在第一行是 returned,因此给人一种同步 运行ning 的错觉。然而,网络 IO 表明这不是(而且不是 - 我把这个条款漏掉了!)。
- 如果我在查询中的
SELECT
语句中添加 TOP 1000
,它的执行速度会快得多。
我不确定为什么这是 运行 同步,并且 Microsoft 站点上的文档似乎很差!
您的原始 SQL 不提供用于分页的游标,因此 SQL 服务器必须 return 一次性获得整个结果。
作为参考,GSerg 在评论中建议我的查询可能无法流式传输。在这种情况下,在查询中使用 LEFT OUTER JOIN 导致在 SQL 服务器中使用哈希匹配。这会阻止查询结果集流式传输。
我正在编写一个 C# 方法,该方法从 SQL 查询(不是直接的 DBSet<T>
!)流式传输大量行,对它们执行一些转换,然后将结果写入MongoDB 数据库。我正在尝试尽快完成此 运行,并且由于相当高的网络延迟,我想避免多次 return 访问 SQL 服务器。
我有一个 class、StreamlinedCrmTicket
,它表示一个 DTO,EF 将原始 SQL 查询的结果投射到该 DTO 上,该查询不采用参数化输入。我正在使用 EF Core 3.1.6 和 .Set<StreamlinedCrmTicket>
技术来执行原始 SQL 查询。然后我使用 .AsNoTracking()
来提高性能,因为这只是一个读取操作。最后,我调用 .AsAsyncEnumerable()
,并将整个 shebang 包装在 await foreach
中,而后者又位于标记为 async
.
整个事情看起来像这样:
await foreach (var ticket in _affinityContext.Set<StreamlinedCrmTicket>().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken))
{
// Do something with each ticket.
}
我的原始 SQL 查询的来源 table 当前包含大约 120 万行。使用 SSMS 测量时,有少数联接似乎对查询的执行时间造成的变化很小。
当我执行我的代码时,似乎 EF 启动了查询,但是 foreach 循环的主体,无论它包含什么,直到整个查询执行完毕并且从 SQL服务器。这违背了我使用 IAsyncEnumerable 的目的!我的理解是 IAsyncEnumerable 应该允许我对行(或实体)进行操作,因为它们来自数据库 return,而不必等待整个结果集。
一些支持我的理论的观点是:
- 对
_affinityContext.Set<StreamlinedCrmTicket>().FromSqlRaw(query).AsNoTracking().AsAsyncEnumerable().WithCancellation(cancellationToken)
的调用一结束,大量的网络IO就开始了。我可以在我的 Windows 机器上的性能监视器中看到 IO 是一个 SQL 服务器连接到服务器我的代码应该 运行ning 反对。 - 我将
foreach
循环的主体交换为一个非常简单的循环,一旦网络 IO 停止,它只需要 运行s。 - 我从 SQL 查询中删除了所有
ORDER BY
子句 - 行排序在此用例中无关紧要,我担心这可能会导致查询在第一行是 returned,因此给人一种同步 运行ning 的错觉。然而,网络 IO 表明这不是(而且不是 - 我把这个条款漏掉了!)。 - 如果我在查询中的
SELECT
语句中添加TOP 1000
,它的执行速度会快得多。
我不确定为什么这是 运行 同步,并且 Microsoft 站点上的文档似乎很差!
您的原始 SQL 不提供用于分页的游标,因此 SQL 服务器必须 return 一次性获得整个结果。
作为参考,GSerg 在评论中建议我的查询可能无法流式传输。在这种情况下,在查询中使用 LEFT OUTER JOIN 导致在 SQL 服务器中使用哈希匹配。这会阻止查询结果集流式传输。