我正在编写一个 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
中,而后者又存在于一个标记为 的方法中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 停止后运行。 - 我
ORDER BY
从 SQL 查询中删除了所有子句——在这个用例中行排序无关紧要,我担心这可能会导致查询在返回第一行之前需要很长时间,从而产生同步运行的错觉。然而,网络 IO 表明这不是(而且不是 - 我把条款省略了!)的情况。 - 如果我在查询中添加一个
TOP 1000
语句SELECT
,它的执行速度会更快。
我不确定为什么这是同步运行的,而且微软网站上的文档似乎很差!