Skip to content

Failed transaction unexpectedly leaves data in table #3736

Closed

Description

@garfur42

Describe the bug

Running several queries in a transaction with separate calls, one query having a conversion error, will fail at the commit step with an exception. Data inserted before the query having the conversion failure will be removed. Data inserted after the query having the conversion failure will still be present.

System.InvalidOperationException: This SqlTransaction has completed; it is no longer usable.
   at Microsoft.Data.SqlClient.SqlTransaction.ZombieCheck()
   at Microsoft.Data.SqlClient.SqlTransaction.Commit()

To reproduce

/*

The following two tables need to be present

create table tab1(
    [Id] int identity(1,1) not null,
	[Val] varchar(10) not null
) on [primary]

create table tab2(
	[Id] int identity(1,1) not null,
	[Val1] int not null,
	[Val2] int not null
) on [primary]

insert into tab1 (Val) values ('12345')
insert into tab1 (Val) values ('42-43') 


Note: The following works as I would expect in SSMS.

--set xact_abort off/on -- Makes no difference
begin transaction
insert into tab2 (Val1, Val2) values (42, 43)
select Id from tab1 where Val = 12345
insert into tab2 (Val1, Val2) values (100, 200)
commit transaction

At the end of transaction, nothing has been inserted.

Running the program below will report an error but still insert a row.

*/

using Microsoft.Data.SqlClient;
using System.Diagnostics.Tracing;

const string CONNECTION_STRING = "Data Source=...;Initial Catalog=...;User ID=...;Password=...;Encrypt=False";


try
{
    using var listener = new SqlClientListener();

    using SqlConnection connection = new(CONNECTION_STRING);

    connection.Open();

    using SqlTransaction transaction = connection.BeginTransaction();

    SqlCommand cmd1 = new("insert into tab2 (Val1, Val2) values (42, 43)", connection, transaction);
    cmd1.ExecuteNonQuery();

    // This query will have a server response with the message;
    //
    //   'Conversion failed when converting the varchar value '42-43' to data type int.'
    //
    SqlCommand cmd2 = new("select Id from tab1 where Val = 12345", connection, transaction);
    var id = cmd2.ExecuteScalar();
    Console.WriteLine($"id={id}");  // 'id' is fine...

    // This works
    SqlCommand cmd3 = new("insert into tab2 (Val1, Val2) values (100, 200)", connection, transaction);
    cmd3.ExecuteNonQuery();

    // This fails with an exception
    transaction.Commit();
}
catch (Exception ex)
{
    Console.WriteLine(ex.ToString());
}
// At completion;
// The first "insert into..." *will not* be present in table "tab2"
// The second "insert into..." *will be* present in table "tab2"


public class SqlClientListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name.Equals("Microsoft.Data.SqlClient.EventSource"))
        {
            EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        Console.WriteLine(eventData.Payload?[0] ?? "<null>");
    }
}

----- project-file
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
  </ItemGroup>

</Project>

Expected behavior

I expected that no insertions would be present in table after the execution of the program.

Further technical details

.NET SDK:
Version: 8.0.318
Commit: e7c684c866
Workload version: 8.0.300-manifests.d6fa65f0
MSBuild version: 17.10.46+aa888d321

Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows
RID: win-x64

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.32
Microsoft.AspNetCore.App 6.0.3
Microsoft.AspNetCore.App 6.0.30
Microsoft.AspNetCore.App 7.0.19
Microsoft.AspNetCore.App 7.0.20
Microsoft.AspNetCore.App 8.0.5
Microsoft.AspNetCore.App 8.0.21
Microsoft.NETCore.App 3.1.32
Microsoft.NETCore.App 6.0.3
Microsoft.NETCore.App 6.0.30
Microsoft.NETCore.App 7.0.19
Microsoft.NETCore.App 7.0.20
Microsoft.NETCore.App 8.0.5
Microsoft.NETCore.App 8.0.21
Microsoft.WindowsDesktop.App 3.1.32
Microsoft.WindowsDesktop.App 6.0.3
Microsoft.WindowsDesktop.App 6.0.30
Microsoft.WindowsDesktop.App 7.0.19
Microsoft.WindowsDesktop.App 7.0.20
Microsoft.WindowsDesktop.App 8.0.5
Microsoft.WindowsDesktop.App 8.0.21

Activity

  1. moved this from To triage to In Discussion in SqlClient Boardon Nov 4, 2025
  2. priyankatiwari08 commented on Nov 5, 2025

    @priyankatiwari08
    Contributor

    We have successfully reproduced the issue and confirmed it as a bug. Our team is working on a fix and will share further updates as soon as they are available.

  3. moved this from In Discussion to Backlog in SqlClient Boardon Nov 11, 2025
  4. samsharma2700 commented on Jan 22, 2026

    @samsharma2700
    Contributor

    Hi @garfur42, thank you for bringing this to our attention.

    I've finished initial investigation of the issue and identified the root cause. Here's what's happening:

    Root Cause
    When ExecuteScalar() is called (cmd2 in your repro), the server sends both partial data and an error. However, ExecuteScalar() reads the first row/column value and closes the SqlDataReader before the error packet is encountered. This causes the exception to be consumed internally rather than propagated to the caller.

    Key Findings

    • The SqlDataReader closes successfully before encountering the error (visible in logs: SqlDataReader.Close occurs before SqlError.ctor)
    • The transaction is correctly marked as "Zombied" (error code 245), but the exception from cmd2 is swallowed
    • When we attempt to commit the transaction, it fails with the zombie error
    • This allows cmd3 to execute seemingly successfully, even though cmd2 had issues

    Notes of Driver Behavior

    • ExecuteNonQuery() works correctly and raises the appropriate exception
    • If the problematic row (conversion error) is the first row encountered, the driver behaves as expected

    Workaround
    If this is blocking you, please use ExecuteNonQuery() instead of ExecuteScalar() for now.

    Next Steps
    I'm working on a fix to ensure exceptions are properly propagated in ExecuteScalar(). I'll update this thread with more details soon.

    SqlCommand.Set_Transaction | API | Object Id 4, Internal Transaction Id 223338299393, Client Connection Id 91b4d7ad-8beb-47d6-a05b-d13c5d8243b6
    2026-01-22T22:54:40.9719422Z cmd2 ExecuteScalar START (conversion error expected)
    SqlCommand.ExecuteScalar | API | ObjectId 4
    SqlCommand.ExecuteScalar | API | Correlation | Object Id 4, Activity Id 3ecaa512-be97-4a49-bc1e-ba5f7a044d60:2, Client Connection Id 91b4d7ad-8beb-47d6-a05b-d13c5d8243b6, Command Text 'select Id from dbo.tab1 where Val = 12345'
    4
    <sc.SqlConfigurableRetryLogicManager.CommandProvider|INFO> Requested the CommandProvider value.
    SqlCommand.RunExecuteReaderTds | Info | Object Id 4, Activity Id 3ecaa512-be97-4a49-bc1e-ba5f7a044d60:2, Client Connection Id 91b4d7ad-8beb-47d6-a05b-d13c5d8243b6, Command executed as SQLBATCH, Command Text 'select Id from dbo.tab1 where Val = 12345'
    SqlDataReader.TryReadInternal | API | Object Id 1
    Exit Scope 5
    SqlDataReader.Close | API | Object Id 1, Command Object Id 4
    SqlError.ctor | ERR | Info Number 245, Error State 1, Error Class 16, Error Message 'Conversion failed when converting the varchar value '42-43' to data type int.', Procedure '', Line Number 1, Batch Index -1
    Exit Scope 6
    4
    Exit Scope 4
    2026-01-22T22:54:40.9828552Z cmd2 ExecuteScalar END result=1

  5. vonzshik commented on Jan 23, 2026

    @vonzshik

    This seems to be the exact same thing as #458, which is an almost 6 year old bug.

  6. 1 remaining item

  7. added a commit that references this issue on Feb 3, 2026
    a0357b2
  8. edwardneal commented on Feb 3, 2026

    @edwardneal
    Contributor

    @vonzshik to back this up, I've just tested the reproduction case from #458. I could still reproduce the problem from the other issue before @samsharma2700 merged #3912, and it seems to be fixed afterwards.

  9. samsharma2700 commented on Feb 4, 2026

    @samsharma2700
    Contributor

    @edwardneal Thank you so much for the confirmation! Glad fix is working.

  10. added this to the 7.0.0-preview4 milestone on Feb 11, 2026
  11. added a commit that references this issue on Feb 26, 2026
    3ac4434
  12. linked a pull request that will close this issue[6.1] Port #3912 to release/6.1 #4133on Apr 6, 2026
  13. moved this from Done to In progress in SqlClient Boardon Apr 6, 2026
  14. moved this from Done to In progress in SqlClient Boardon Apr 6, 2026
  15. moved this from In progress to Done in SqlClient Boardon Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions