InvalidOperationException: There is already an open DataReader associated with this Connection which must be closed first.
— an EF Core query ran inside a foreach that was still reading another query
on the same SQL Server connection. Put .ToList() on the outer query so its
rows are read to the end before the loop body runs.
Adding MultipleActiveResultSets=True to the connection string also made the
same loop run in the reproduction. ToList() is the smaller change: it needs no
connection-string edit and it shows in the code where the data is loaded.
The error
PS> dotnet run -- bad
...
Unhandled exception. System.InvalidOperationException: There is already an open DataReader associated with this Connection which must be closed first.
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
at Microsoft.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
...
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Program.<Main>$(String[] args) in ...\EfMars\Program.cs:line 25
Dropped: True
The cuts hide NuGet audit warnings printed by dotnet run and SqlClient and EF
Core frames. Dropped: True comes from the finally block that
deletes the test database.
Why it happens
foreach (var c in db.Customers)
{
var count = db.Orders.Count(o => o.CustomerId == c.Id);
Console.WriteLine($"{c.Name}: {count} orders");
}
foreach (var c in db.Customers) enumerated the customers query directly, so
its reader was still open while the loop body ran. Line 25 is the Count()
inside the loop: a second command on the same connection. The top frame of the trace is
SqlClient's ValidateConnectionForExecute, and the message says what it found:
a reader already open on that connection.
The fix
foreach (var c in db.Customers.ToList())
{
var count = db.Orders.Count(o => o.CustomerId == c.Id);
Console.WriteLine($"{c.Name}: {count} orders");
}
PS> dotnet run -- fix
...
Maria Garcia: 2 orders
David Chen: 1 orders
Dropped: True
ToList() read both customers into memory and closed the reader, so each
Count() had the connection to itself. The alternative, run with the original
loop and only the connection string changed:
if (mode == "mars") cs += ";MultipleActiveResultSets=True";
PS> dotnet run -- mars
...
Maria Garcia: 2 orders
David Chen: 1 orders
Dropped: True
Both gave the same output, two orders for Maria Garcia and one for David Chen. Both
versions still run one Count() query per customer inside the loop; the fixes
change when the customers are read, not how many queries the loop sends.
How it was reproduced
A console project from dotnet new console -n EfMars on .NET SDK 10.0.403 with
Microsoft.EntityFrameworkCore.SqlServer 10.0.14 (which brought in Microsoft.Data.SqlClient
6.1.7), against SQL Server LocalDB (localdb)\MSSQLLocalDB with a test database
fix8_ef that each run created, seeded with two fictional customers and three
orders, and deleted. The connection string was
Server=(localdb)\MSSQLLocalDB;Database=fix8_ef;Trusted_Connection=True.
Every run, the failing one included, printed Dropped: True, and a
sqlcmd query on sys.databases afterwards returned no row for
fix8_ef, so the test left nothing behind on the LocalDB instance.
Frequently asked
- How do I fix There is already an open DataReader associated with this Connection in EF Core?
- Materialise the outer query with ToList before the loop, so its reader is closed before the queries inside the loop run. In the reproduction that alone fixed it.
- Does MultipleActiveResultSets=True fix the open DataReader error?
- In the reproduction it did: the same foreach with a Count query inside ran without error against LocalDB once MultipleActiveResultSets=True was added to the connection string.
- Why does the open DataReader error point at a line inside my foreach?
- Because the second query, inside the loop, is the one SqlClient refused. In the reproduction it was line 25, the Count call, while the outer foreach still held the first reader open.
More decoded errors in the Fixes category.