ExecuteScalar does not always raise an exception when selected as deadlock victim
See original GitHub issueDescribe the bug
We have encountered an issue in production where ExecuteScalar run in context of a RepeatableRead transaction intermittently fails to raise an exception when SQL Server selects it as a deadlock victim and rolls back the transaction. The transaction state is correctly set to Aborted, but the lack of an exception confuses application code and libraries like EF Core and who assume (fairly reasonably) that no exception means success.
Additionally, ExecuteScalar is returning the value that would be expected for a successful transaction even though the transaction has been rolled back.
The issue does not seem to occur when using ExecuteReader or ExecuteNonQuery.
I have a reduced repro case which consistently reproduces the error on SQL Server 12 and 13, tested across multiple machines. The issue reproduces with System.Data.SqlClient/dotnet core 2.1 and Microsoft.Data.SqlClient/dotnet core 3.1.
To reproduce
-
Clone the repository https://github.com/deadalusai/SqlClient-Aborted-Transaction-Repro
-
Create a new database using the
ZombieDb.sqlscript -
Run the test program:
For .NET Core 3.1 with Microsoft.Data.SqlClient:
> dotnet run -p .\ZombieTester.NET31.csprojFor .NET Core 2.1 with System.Data.SqlClient:
> dotnet run -p .\ZombieTester.NET21.csproj -
Press any key to start testing for the error case. The test will run until it reproduces the issue or times out.
Expected behavior
A SqlException to be raised when a command is selected as a deadlock victim.
Further technical details
Microsoft.Data.SqlClient version: 1.1.1 System.Data.SqlClient version: 4.8.1 .NET target: .NET Core 3.1, .NET Core 2.1 SQL Server version: SQL Server 2017, SQL Server 2016 Operating system: Windows 10
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:13 (4 by maintainers)

Top Related StackOverflow Question
Hello! Tested @deadalusai’s example with SqlClient 3.0, the problem is still reproducible. Did a small research, and it looks like SqlClient reports the error depending on when exactly that error is read - either on
SqlDataReader.Read(thenTdsParser.TryRunis called withRunBehavior.ReturnImmediately) or onSqlDataReader.Close(thenTdsParser.TryRunis called withRunBehavior.Clean, which preventsTdsParser.TryRunfrom throwing that error).We’ve had some success with prepending something like the following before every insert/update/delete SQL operation:
For SqlClient you just prepend it to the command text. For EF Core 3.0 you can use interceptors: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/#interception-of-database-operations