在使用 ASP.NET Core 3.0 和 SQL Server 2019+ 时,我什么时候应该使用 MultipleActiveResultSets=True?

When should I use MultipleActiveResultSets=True when working with ASP.NET Core 3.0 and SQL Server 2019+?

我编写的大多数应用程序都不使用 MultipleActiveResultSets=True,但我看到在其中几个应用程序和一些教程中启用了该选项。

这个 SO question 涉及相同的主题,但它很老,我相信在此期间事情发生了很大变化。

OP 争论在执行 ExecuteReader 时执行一些非查询。在这种情况下,我认为这是一个糟糕的设计,因为它可能会被一些批处理式操作所取代,也许是一个存储过程来最小化往返次数。

当将 Entity Framework 与 ASP.NET Core 一起使用并收到与数据上下文相关的异常时,已经在范围内执行某些操作,我将其视为一个错误,而不是考虑启用 MARS。

读这篇MS Docs article我看到应该注意各个方面,例如选项(ANSI_NULLSDATE_FORMATLANGUAGETEXTSIZE) 、安全上下文、当前数据库、状态变量(@@ERROR@@ROWCOUNT@@FETCH_STATUS@@IDENTITY)在启用 MARS 的情况下工作。

此外,10 年以上意味着功能更强大的服务器能够在确实需要时容纳更多的连接(缓存应该有助于减少这种需求)。

所以我想知道在使用现代 ASP.NET 核心应用程序 (3.0+) 时是否必须考虑启用 MARS。

问题: 在使用 ASP.NET Core 3.0 和 SQL Server 2019+ 时,我什么时候应该使用 MultipleActiveResultSets=True?

编辑以解决反馈

我对详尽的分析不感兴趣,但有几个适当的上下文来证明是否使用 MARS。

ASP.NET 核心应用程序中的一个典型示例是将数据库上下文设置为范围(每个请求从连接池获取一个数据库连接,进行更改,通常每个 request/scope 一个事务)。到目前为止,为了避免 MARS,我将与每个连接的多个查询相关的错误视为我自己的错误,但我这样做并没有真正理解为什么。

是的,MARS 在现代数据访问框架中仍然占有一席之地,因为它们为以下两个主要的一般查询问题提供了(有效的)解决方案 - 流式传输(即非缓冲)(1) 具有预加载相关数据集合的数据(2) 延迟加载任何类型的相关数据。


在这两种情况下,执行查询都应提供 IEnumerator<T>(或其异步版本),它是数据 reader(或数据库前向只读游标)的等效对象。因此,每个 MoveNext{Async} 都应映射到数据 reader 的 ReadNext,并且预计会提供一个完全填充的 T、w/o 缓冲所有其他数据。为了实现这一点,基础数据 reader 必须在枚举期间保持打开状态,并在完成或提前中止时关闭(例如,FirstOrDefault())——原因之一 IEnumerator<T>IDisposable.

现在想象一下如果启用延迟加载会发生什么。您获得一些实体并访问一些导航 属性。这就触发了懒加载操作,当然需要执行reader从数据库中获取数据。由于外部 reader 仍处于打开状态(活动),因此 w/o MARS 此操作将因运行时异常而失败。不好。除了提前缓冲所有内容(基本上切换到快照模式)或不使用延迟加载之外,您或框架无能为力。

假设您不使用延迟加载(无论如何都不推荐)。但是您的实体包含相关的数据集合并且您希望预先加载它们。关系数据库 SQL 提供平面结果集,即不支持查询结果集中的“嵌套集合”。那么如何流式传输这些数据呢?

基本上有两种解决方案。

首先是基于单个 SQL 查询,其中包含所有主 + 相关 table 列,以及 returns 某种混合记录,其中某些字段适用于特定结果和其他是空值。 EF6 和 EF Core 3.0+ 使用此方法。 EF Core 1.x/2.x 使用另一种方法,而 EF Core 5.0 允许您在两者之间进行选择。为什么?因为当你有多个子集合时,这往往会产生非常低效的查询(执行和处理结果集,因为它传输了大量不必要的数据)。

其次是使用单独的查询 - 一个用于主要结果集,一个用于每个相关的子集合。这个想法很简单。由于通常 PK 和 FK 都被索引,数据库可以有效地 return 它们使用索引按这些列排序(无论如何 join 操作都需要),然后它们可以很容易地通过读取在客户端合并提前(缓冲)最多一条记录。

听起来不错,不是吗?有一个小但重要的警告 - 它需要 MARS!否则,它必须切换到缓冲模式。这完全违背了 IEnumerator 的想法,在异步版本中 - 取消概念。我在的回答中可以看到后者的效果,最后建议启用MARS。

有关 EF Core 查询的详细信息,请参阅官方 EF Core 文档的 Split queries and How Queries Work (and basically the whole Query data) 部分。

旁注 单独的连接并不是真正的选择,尤其是当需要像 repeatable 读取这样的事务级别时。 MARS 提供了连接所需的精确抽象。 SP 内部的 AFAIK 可以同时打开任意数量的游标,因此不确定 ADO 连接层的确切问题是什么以及为什么 MARS 被认为是需要启用的可选功能,而不仅仅是开箱即用的功能。 ORM 虽然可以在适用时尝试在幕后使用单独的连接。 EF Core 目前没有。


所以简短地回顾一下,如果您不使用延迟加载(可能)并且没有相关集合(不太可能 - 一对多很常见,相关集合并不一定意味着导航属性 和 Include - 同样适用于列表和类似成员的投影),那么您不需要 MARS。否则最好启用它们 - 它们是功能,所以使用它。