【问题标题】:How can I abort a running EF Core Query using GetAsyncEnumerator?如何使用 GetAsyncEnumerator 中止正在运行的 EF Core 查询?
【发布时间】:2021-10-15 11:04:49
【问题描述】:

我使用的是 EF Core 5.0 并且有以下代码:

public async IAsyncEnumerable<Item> GetItems([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    await using var ctx = _DbContextFunc();
    //Isolationlevel is required to not cause any issues with parallel working on already read items
    await ctx.Database.BeginTransactionAsync(IsolationLevel.ReadUncommitted, cancellationToken).ConfigureAwait(false);
    await foreach (var item in ctx.Item.
        .AsSplitQuery()
        .Include(i => i.ItemDetail1)
        .Include(i => i.ItemDetail2)
        .OrderByDescending(i => i.ItemId)
        .AsNoTracking()
        .AsAsyncEnumerable()
        .WithCancellation(cancellationToken))
    {
        yield return item;
    }
}

它按预期工作,允许我在加载更多数据的同时填充数据网格。如果我取消提供的 CancelationToken,首先我会在预期的 MoveNextAsync() 行上得到一个 TaskCanceledException。

但是:我可以在 SQL Profiler 中看到 SQL 查询本身并没有中止,而是一直运行,直到所有数据都加载完毕,然后我才在同一行收到第二个 TaskCanceledException。

如何中止查询本身?

更新

我将 AsSplitQuery() 添加到示例中,因为事实证明这是我所经历的行为的原因(正如 Ivan 正确猜测的那样)。为了使样本更短而忽略了它...

【问题讨论】:

  • 要知道在OrderByDescending 查询中提供哪个项目,它是否必须查询整个表才能只提供一个结果?
  • 为什么你认为这个查询只返回一个项目?
  • 我的意思是,使用 yield 关键字一次返回一个。我想这可以让你停止枚举器枚举,但枚举器不需要执行整个 SQL 事务来产生一个项目吗?
  • 如果不出意外,也许您可​​以将您的交易包装在using(var tran = await ctx.Database.BeginTransactionAsync(IsolationLevel.ReadUncommitted, cancellationToken).ConfigureAwait(false)) { ... } 中,然后在处理取消令牌时,您可以尝试启动tran.Rollback()。我不知道这是否可行,但这是我目前唯一能想到的。
  • 不,它作为流很好地工作,我可以从探查器中看到,即使提供了第一个结果,查询也没有完成(原始查询比这里显示的要复杂一些)但这不应该是问题)。我会尝试回滚并告诉你它有帮助

标签: c# entity-framework-core


【解决方案1】:

所描述的行为是特定于某些类型的查询的 EF Core 实现,这些查询在内部使用所谓的 buffered data reader - 一个 DbDataReader 实现,它最初是 fully consumes the underlying data reader 并缓冲结果,因此可以更早发布底层数据读取器.

很难确切地说出哪种类型的查询使用“设计”,但当Multiple Active Result Sets (MARS) 被禁用/不支持时,split queries 肯定会使用它。

为什么?拆分查询执行多个数据库查询并合并它们的结果,就好像它们是单个查询一样。合并需要同时激活多个数据读取器。当底层数据库提供程序不支持 MARS 或禁用 MARS(默认情况下)时,在存在活动读取器时尝试执行第二个读取器会导致运行时异常。因此,为了解决这个问题,EF Core 必须消耗和缓冲活动的阅读器并在执行下一个之前释放(关闭/处置)它。

由于需要此功能才能使该功能正常工作,因此没有外部方法可以控制它。除非数据库提供程序支持 MARS(例如,SqlServer 支持),在这种情况下,您可以通过添加 enable it in the connection string

MultipleActiveResultSets=True

拆分查询将直接使用底层数据读取器,因此可以在不完全消耗它们的情况下提前取消。

【讨论】:

    猜你喜欢
    • 2022-09-13
    • 2023-03-19
    • 2017-10-12
    • 2019-03-22
    • 2021-04-30
    • 1970-01-01
    • 2020-04-11
    • 1970-01-01
    • 2011-04-16
    相关资源
    最近更新 更多