System.Transactions.TransactionScope の注意点

今さらですが、何点か記載します。

  • TransactionScope のインスタンスを生成してから、コネクションを開くこと。
  • TransactionScope 内で複数の接続を開くと、分散トランザクション(MSDTC)扱いになる。

これは次のコードの様に、同一の接続を開いて閉じて開いても分散トランザクション扱いになる。

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(connectionString))
{
  conn.Open();
  conn.Close();
  conn.Open();
}

但し、.NET Framework 2.0 SP1 (以上?)、SQL Server 2008 の組み合わせだと分散トランザクションにならないみたい。
テーブルアダプタと TransactionScope の組み合わせ を参考に、実際に試してみました。
(試した環境は、.NET Framework 3.5 SP1、SQL Server 2008 Developer Edition、SQL Server 2005 Express Edition 後から追記

  • TransactionScope のコミットは、Dispose のタイミングで行われる。

Complete メソッドを呼び出したタイミングではない。Complete メソッドはフラグを立てるだけの役割で、Dispose 時にフラグが立ってるかどうかで判断する。(はず)
なので、using ステートメント を使わず、Dispose も呼ばない TransactionScope はいつコミットされるか分からない。

  • TransactionScope は、スレッドを跨げない。

次のコードは、TransactionScope のトランザクションは効いていない。(こんなコードを通常使うかどうかは別とする。)
修正 サンプルコード一部コメントアウト。Complete メソッドの呼び出し部分

using System;
using System.Data.SqlClient;
using System.Transactions;
using System.Threading;

namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      string connectionString = @"〜";
      // TransactionScope の生成と破棄は、メインスレッドで行い、更新処理は別スレッドで行う。
      using (ThreadProc proc = new ThreadProc(connectionString))
      {
        Thread t = new Thread(proc.Start);
        t.IsBackground = true;
        t.Start();
        t.Join();
      }
      Console.ReadKey();
    }
  }
  public class ThreadProc : IDisposable
  {
    string connectionString;
    TransactionScope scope;
    public ThreadProc(string connectionString)
    {
      this.connectionString = connectionString;
      this.scope = new TransactionScope();
    }
    public void Start()
    {
      using (SqlConnection conn = new SqlConnection(connectionString))
      using (SqlCommand cmd = new SqlCommand(@"INSERT 〜", conn))
      {
        conn.Open();
        cmd.ExecuteNonQuery();
      }
    }
    public void Dispose()
    {
      if (this.scope != null)
      {
        // this.scope.Complete();
        this.scope.Dispose();
      }
    }
  }
}

また次のコードは、「TransactionScope は、作成されたスレッド上で破棄される必要があります。」というメッセージのInvalidOperationException が発生する。

using System;
using System.Data.SqlClient;
using System.Transactions;
using System.Threading;

namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      string connectionString = @"Data Source=NEW-SHINSUKE;Initial Catalog=SmileCalendar;Integrated Security=True";
      // TransactionScope の生成と更新処理は別スレッドで行い、破棄はメインスレッドで行う。
      using (ThreadProc proc = new ThreadProc(connectionString))
      {
        Thread t = new Thread(proc.Start);
        t.IsBackground = true;
        t.Start();
        t.Join();
      }
      Console.ReadKey();
    }
  }
  public class ThreadProc : IDisposable
  {
    string connectionString;
    TransactionScope scope;
    public ThreadProc(string connectionString)
    {
      this.connectionString = connectionString;
    }
    public void Start()
    {
      this.scope = new TransactionScope();
      using (SqlConnection conn = new SqlConnection(connectionString))
      using (SqlCommand cmd = new SqlCommand(@"INSERT 〜", conn))
      {
        conn.Open();
        cmd.ExecuteNonQuery();
      }
    }
    public void Dispose()
    {
      if (this.scope != null)
      {
        // this.scope.Complete();
        this.scope.Dispose();
      }
    }
  }
}

SQL Server と System.Transactions の統合 (ADO.NET) から引用

プログラミング上の強化に加えて、System.Transactions と ADO.NET の連係により、トランザクション処理が最適化されます。昇格可能なトランザクションとは、必要に応じて完全な分散トランザクションに自動的に昇格する、軽量の (ローカル) トランザクションです。

ADO.NET 2.0 以降では、SQL Server 2005 を組み合わせて使用した場合、System.Data.SqlClient によって昇格可能なトランザクションがサポートされます。昇格可能なトランザクションは、必要な場合以外、分散トランザクションのオーバーヘッドの増加を引き起こすことはありません。昇格可能なトランザクションは自動的に処理され、開発者による介入は必要ありません。

これ今日迄気付かなかった、めっちゃ失敗。本番環境が SQL Server 2000 の物に TransactionScope を使って実装してて分散トランザクションがサポートされてないよって怒られた。
(検証環境は、SQL Server 2008 を使ってた。そもそもそれが間違いなんだが。。)