The problem here is that the SqlConnection.BeginTransaction that does not take parameters defaults to read committed. I guess we didn't understand what the "default isolation level" text is on that page.
That page has this text:
If you do not specify an isolation level, the default isolation level is used. To specify an isolation level with the BeginTransaction method, use the overload that takes the iso parameter (BeginTransaction). The isolation level set for a transaction persists after the transaction is completed and until the connection is closed or disposed. Setting the isolation level to Snapshot in a database where the snapshot isolation level is not enabled does not throw an exception. The transaction will complete using the default isolation level.
(my highlight)
Here's a LINQPad script that demonstrates:
void Main()
{
using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated security=true"))
{
conn.Open();
Dump(conn, "after open");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
cmd.ExecuteNonQuery();
}
Dump(conn, "after set iso");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "BEGIN TRANSACTION";
cmd.ExecuteNonQuery();
}
Dump(conn, "after sql-based begin transaction");
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "COMMIT";
cmd.ExecuteNonQuery();
}
Dump(conn, "after sql-based commit");
var trans = conn.BeginTransaction();
Dump(conn, "after .net begin transaction", trans);
trans.Commit();
Dump(conn, "after .net commit");
}
}
public static void Dump(SqlConnection connection, string title, SqlTransaction transaction = null)
{
using (var cmd = new SqlCommand())
{
cmd.Connection = connection;
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = "SELECT transaction_isolation_level FROM sys.dm_exec_sessions WHERE session_id = @@SPID";
Debug.WriteLine(title + "=" + Convert.ToInt32(cmd.ExecuteScalar()));
}
}
It will output:
after open=2
after set iso=1
after sql-based begin transaction=1
after sql-based commit=1
after .net begin transaction=2
after .net commit=2
Here you can see that manually beginning and committing a transaction through SQL would not change the isolation level, but beginning a transaction in .NET without explicitly stating the isolation level still changes it to read committed.
Since everywhere we read, starting a transaction without explicitly stating the isolation level said that it inherited the isolation level of the session, I guess we didn't understand that .NET would not do the same.