C# 如何使用GetAsyncEnumerator中止正在运行的EF核心查询?
我使用的是EF Core 5.0,代码如下:C# 如何使用GetAsyncEnumerator中止正在运行的EF核心查询?,c#,entity-framework-core,C#,Entity Framework Core,我使用的是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 w
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;
}
}
公共异步IAsyncEnumerable GetItems([EnumeratorCancellation]CancellationToken CancellationToken=default)
{
使用var ctx=_DbContextFunc()等待;
//要求Isolationlevel在并行处理已读项目时不会导致任何问题
wait ctx.Database.BeginTransactionAsync(IsolationLevel.ReadUncommitted,cancellationToken).ConfigureWait(false);
等待foreach(ctx.item中的变量项)。
.AsSplitQuery()
.Include(i=>i.ItemDetail1)
.Include(i=>i.ItemDetail2)
.OrderByDescending(i=>i.ItemId)
.AsNoTracking()
.AsAsAsyncEnumerable()
.WithCancellation(cancellationToken))
{
收益回报项目;
}
}
它按预期工作,允许我在加载更多数据的同时填充datagrid。如果我取消提供的CancelationToken,首先我会在MoveNextAsync()行上得到一个TaskCanceledException,这是预期的
但是:我可以在SQL Profiler中看到,SQL查询本身并没有中止,而是始终运行,直到加载了所有数据,然后我才在同一行上得到第二个TaskCanceledException
如何中止查询本身
更新
我将AsSplitQuery()添加到示例中,因为它是我所经历的行为的原因(正如Ivan正确地猜测的)。为了缩短示例,我们省略了它…所描述的行为是特定于某些类型的查询的EF核心实现,这些查询在内部使用了所谓的-a
DbDataReader
实现,该实现对结果进行初始化和缓冲,因此可以更早地发布底层数据读取器
很难说哪种类型的查询使用“按设计”的方式,但它肯定是在禁用/不支持时使用的
为什么??拆分查询执行多个数据库查询,并将其结果合并为单个查询。整合需要同时激活多个数据读取器。当基础数据库提供程序不支持MAR或禁用MAR(默认情况下)时,在存在活动读卡器时尝试执行第二个读卡器会导致运行时异常。因此,为了解决这个问题,EF核心必须消耗和缓冲活动读取器,并在执行下一个读取器之前释放(关闭/处置)它
由于这是使功能正常工作所必需的,因此没有外部控制方法。除非数据库提供程序支持MARS(例如,SqlServer支持),在这种情况下,您可以通过添加
MultipleActiveResultSets=True
而拆分查询将直接使用底层数据读取器,因此可以在不完全使用它们的情况下提前取消。要知道在
OrderByDescending
查询中提供哪一项,它是否必须查询整个表才能只提供一个结果?为什么您认为此查询只返回一项?我的意思是,使用yield
关键字一次返回一个。我可以想象,这将允许您停止枚举器的枚举,但是枚举器不需要执行整个SQL事务来生成一个项目吗?如果没有其他内容,您可以使用(var tran=wait ctx.Database.BeginTransactionAsync(IsolationLevel.ReadUncommitted,cancellationToken)将事务包装在中.ConfigureAwait(false)){…}
然后在处理取消令牌时,您可以尝试启动tran.Rollback()
。我不知道这是否可行,但这是我目前唯一能想到的。不,它作为一个流很好地工作,我可以从分析器中看到,即使提供了第一个结果,查询也没有完成(原始查询比这里显示的要复杂一些,但这不应该是问题)。我将尝试回滚,并让您知道它有帮助