【发布时间】:2020-11-17 05:51:04
【问题描述】:
我正在编写一个 C# 方法,它从 SQL 查询(不是直接的DBSet<T>!)流式传输大量行,对它们执行一些转换,然后将结果写入 MongoDB 数据库。我试图让这个运行尽可能快,由于相当高的网络延迟,我想避免多次返回 SQL Server。
我有一个类 StreamlinedCrmTicket,它表示一个 DTO,EF 将原始 SQL 查询的结果投影到该 DTO 上,该查询不接受参数化输入。我正在使用 EF Core 3.1.6 和 .Set<StreamlinedCrmTicket> 技术来执行原始 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<StreamlinedCrmTicket>().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