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

1

31

Most applications I have programmed do not use MultipleActiveResultSets=True, but I have seen the option being enabled in a couple of them and in a few tutorials.

This SO question deals with the same topic, but it is very old and I believe that things have changed much in the mean time.

OP argues about executing some non-queries, while performing an ExecuteReader. In this case I believe it to be a bad design since it might be replaced with some batch-style operation, perhaps a stored procedure to minimize the number of round-trips.

When using Entity Framework with ASP.NET Core and receiving an exception related to the data context executing already something in the scope, I treat it as a bug and not thinking about enabling MARS.

Reading this MS Docs article I see that one should pay attention to various aspects such as options (ANSI_NULLS, DATE_FORMAT, LANGUAGE, TEXTSIZE), security context, current database, state variables (@@ERROR, @@ROWCOUNT, @@FETCH_STATUS, @@IDENTITY) when working with MARS enabled.

Also, 10+ years mean much more capable servers being able to hold much more connections if this is really needed (caching should help reduce this need).

So I am wondering if I ever have to consider enabling MARS when working with modern ASP.NET Core applications (3.0+).

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

Edit to address feedback

I am not interested in an exhaustive analysis, but a couple of appropriate contexts to justify using MARS or not.

A typical example in ASP.NET Core applications is to have database context as scoped (get a database connection from the connection pool per request, make changes, usually one transaction per request/scope). So far, I have treated errors related to multiple queries per connection as my own fault to avoid MARS, but I did so without understanding actually why.

Succeed answered 6/1, 2020 at 6:30 Comment(3)
You should enable MARS if you perform operations that require MARS, otherwise those will fail. Whether you or anyone else should write code that uses such operations is subjective. The SQL Server developers saw a need in existing applications and fulfilled it, notwithstanding the fact that all code that uses MARS could also be written to not use it. This was true even back when MARS was introduced and it's still true today.Methodist
@JeroenMostert - by "should" I would understand "it is a best practice". I understand there were some reasons to have MARS a decade ago, but considering current frameworks, software architecture and computing power does it make sense to consider using it? Or shortly, is it a good practice to use it nowadays?Succeed
"Good practice" is another way of saying "give me your (preferably educated) opinions". I dare anyone to present an objective framework in which to evaluate the use of MARS vs. its non-use, and I cast doubt that such a thing would fit in a single answer on SO -- it's simply not obviously good or obviously bad enough for that. Though I wouldn't mind being proven wrong, of course.Methodist
G
37

Yes, MARS still have their place in modern data access frameworks because they provide the (efficient) solution of the following two major general querying problems - streaming (i.e. non buffering) (1) data with eager loaded related data collections and (2) lazy loaded related data of any type.


In both cases, executing a query is expected to provide IEnumerator<T> (or its async version) which is the object equivalent of data reader (or database forward only read only cursor). So each MoveNext{Async} should be mapped to ReadNext of the data reader and is expected provide one fully populated T, w/o buffering ahead all others. In order to achieve that, the underlying data reader must be kept open during the enumeration, and close when it is complete or aborted earlier (for instance, FirstOrDefault()) - one of the reasons IEnumerator<T> is IDisposable.

Now imagine what happens if you have lazy loading enabled. You get some entity and access some navigation property. This triggers lazy load operation, which of course needs to execute reader to get the data from the database. Since the outer reader is still open (active), w/o MARS this operation will simply fail with runtime exception. Not good. And there is nothing you or framework can do other than either buffer everything in advance (basically switching to snapshot mode) or not use lazy loading.

Let say you don't use lazy loading (it's not recommended anyway). But your entities contain related data collections and you want to eager load them. Relational database SQL provide flat result sets, i.e. does not support "nested collections" inside query result set. So how to stream such data?

There are basically two solutions.

First is based on single SQL query which contains all master + correlated table columns, and returns some sort of hybrid records where some fields apply to specific result and other are nulls. This approach is used by EF6 and EF Core 3.0+. While EF Core 1.x/2.x uses the other approach and EF Core 5.0 allows you to choose between the two. Why? Because when you have several sub collections, this tend to produce quite ineffective queries (both execution and processing the result set since it transfers a lot of unnecessary data).

Second is using separate queries - one for the main result set and one for each correlated sub collection. The idea is simple. Since usually both PKs and FKs are indexed, the database can effectively return them ordered by these columns using index (which is needed for join operations anyway), and then they can easily be merged client side by reading ahead (buffering) maximum one record.

Sounds good, isn't it? With one small, but important caveat - it needs MARS! Otherwise, it has to switch to buffering mode. Which totally defeats the idea of IEnumerator and in async version - the cancellation concept. You can see the effect of the latter in my answer to How can I abort a running EF Core Query using GetAsyncEnumerator?, and at the end the suggestion was to enable MARS.

For more info about EF Core querying, see Split queries and How Queries Work (and basically the whole Query data) sections of the official EF Core documentation.

Side note Separate connection isn't really an option, especially if one needs transaction levels like repeatable reads. MARS is providing the exact abstraction needed over connection. And AFAIK inside SP one can open simultaneously as many cursors as they want, hence not sure what's the exact problem with ADO connection layer and why MARS is considered optional feature which needs enabling, rather than just out of the box functionality. The ORM though could try utilizing separate connection behind the scenes when applicable. EF Core currently doesn't.


So to recap shortly, if you don't use lazy loading (likely) and don't have correlated collections (unlikely - one-to-many is quite common, and the correlated collection doesn't necessarily mean navigation property and Include - same applies to projections to list and similar members), then you don't need MARS. Otherwise better enable them - they are feature, so use it.

Gyro answered 10/4, 2021 at 17:19 Comment(3)
Thanks for the example that shows how MARS can be used. So far, I could read all required data (master + correlated) without the need of GetAsyncEnumerator, so I have never felt the need for something like MARS. I think MARS was left disabled by default, because it requires some deeper understanding of what is happening, that is not true parallelism as shown in this article.Succeed
@Alexei-checkCodidact There is nothing to do with parallelism. Nothing more than provide several open cursors at the same time. Again, something which I believe is supported inside T-SQL, so just needs to be exposed to the client API. SqlServer people many times put some non logical constraints, the famous being "cycles or multiple cascade paths", which other databases provide out-of-the box w/o issue, so they definitely are doable. But it is what it is :-)Gyro
As of EF Core 5 introducing support for savepoints, EF generates a warning when MARS is enabled stating it cannot be used with savepoints. As a result, I'm hesitant to assume I should blindly enable MARS. Instead, I'm now leaning toward leaving MARS disabled unless I know I need it and understand the tradeoffs (which right now, frankly, I don't).Barham

© 2022 - 2024 — McMap. All rights reserved.