question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Disposing an EF enumerator takes long time on large/complex queries on SQL Server.

See original GitHub issue

When you have a query that returns lots of records, the disposing of an enumerator takes long time if you aborts the enumeration before traversing through all rows.

I did a simple test project that has a simple table with 2 million rows and executed the following code. The metrics for the disposing was approx. 1 second on my environment when creating a data reader without filtering (other filtering is also done in the test project.

foreach (var t in context.Tests)
{
    break; //Just to stop the iteration directly
} // This takes approx one second when it disposes the enumerator foreach uses

After investigating the EF core code around this, I see that the Dispose method in RelationalDataReader.cs calls Close on the data reader. According to the Remarks section of the SqlDataReader.Close method it states:

The Close method fills in the values for output parameters, return values and RecordsAffected, increasing the time that it takes to close a SqlDataReader that was used to process a large or complex query.

And this is the problem; if you haven’t iterated through the reader to the end, the close method will iterate through the rest of the reader and thus create a heavy performance impact against SQL Server on large/complex queries.

Further, the remarks section follows up with

When the return values and the number of records affected by a query are not significant, the time that it takes to close the SqlDataReader can be reduced by calling the Cancel method of the associated SqlCommand object before calling the Close method.

The number of rows affected isn’t interesting in when iterating, so the disposing logic should call Cancel on the SqlDataReader. I see that this may be an issue to solve this easily in the code as it is, since the RelationalDataReader uses the DbDataReader which doesn’t have an Cancel logic… But again, it is something that heavily impacts performance when certain operations are done, so it should be fixed some way.

This issue has been reproduced with the following environment:

.NET SDK (reflecting any global.json):
 Version:   5.0.201
 Commit:    a09bd5c86c

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19042
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.201\

Host (useful for support):
  Version: 5.0.4
  Commit:  f27d337295

.NET SDKs installed:
  3.1.301 [C:\Program Files\dotnet\sdk]
  3.1.407 [C:\Program Files\dotnet\sdk]
  5.0.104 [C:\Program Files\dotnet\sdk]
  5.0.201 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.26 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

And with Microsoft.EntityFrameworkCore.SqlServer Version 5.0.5

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:14 (9 by maintainers)

github_iconTop GitHub Comments

2reactions
rojicommented, May 8, 2021

When a reader is disposed (e.g. because an EF Enumerator is disposed), any pending results must indeed be consumed - this is necessary in order for the database connection to be usable for another query. In your example app, you execute a query which fetches a huge number of rows, and then immediately dispose the reader; it’s expected for this to be quite slow (the notes on the number of records affected as well as output/return parameters are very unlikely to be relevant for this case).

Systematically calling DbCommand.Cancel on unconsumed readers would most probably not be a good thing, as it would likely negatively impact queries where there isn’t a huge number of pending rows. However, you should be able to trigger this behavior from user code by executing your query asynchronously, and then triggering the cancellation token before disposing the enumerator.

However, at the end of the day, it’s usually bad practice to be selecting rows which you won’t be consuming - it’s recommended to use the Take operator (and paging in general) to fetch rows which you know you’ll need. There are indeed some cases where the number of rows needed isn’t known until you start enumerating the resultset, but these cases should be rare, and it may still be better to simply request more rows using paging (at the cost of additional roundtrips).

0reactions
AndriySvyrydcommented, May 18, 2021

EF 5.0 does have a bug, where RelationalDataReader.DisposeAsync (async) calls DbDataReader.Close (sync). This was already fixed for 6.0 as part of #24207. We can consider patching this.

Unless we get more reports we don’t think there’s enough value in patching this in 5.0.x

Read more comments on GitHub >

github_iconTop Results From Across the Web

Triggering a cancellation token may not cancel a running ...
Disposing an EF enumerator takes long time on large/complex queries on SQL Server. ... to SQL Server when a cancellation token is triggered....
Read more >
Entity Framework query slow, but same SQL in SqlQuery is ...
This almost always worked, then bugged out in rare circumstances, with the query taking several minutes in Entity Framework before it was sent ......
Read more >
Entity Framework Performance and What You Can Do ...
The query generated takes a long time to run, but looking at it, it seems perfectly reasonable. To understand why a query is...
Read more >
Troubleshoot slow-running queries - SQL Server
Check if the time exceeds a threshold you have set (in milliseconds) based on an established performance baseline.
Read more >
Performance Diagnosis - EF Core
The above command took 4 milliseconds. If a certain command takes more than expected, you've found a possible culprit for a performance ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found