TransactionScope のネスト

System.Transactions.TransactionScope は Complete を呼ばない限り Dispose されたときにロールバックしてくれる。 テストとか例外処理とかで便利だ。

using (var ts = new TransactionScope())
{
  // DB に書き込み

  ts.Complete();
}

ネストしてもつかえる。このときネスト内で new された TransactionScope すべてが Complete されてはじめてコミットされる。

void WriteParent(string connectionString) {
  using (var ts = new TransactionScope())
  {
    WriteChild1(); // この時点ではコミットされない

    WriteChild2(); // まだ

    ts.Complete(); // ここでようやくコミット
  }
}

void WriteChild1() {
  using (var ts = new TransactionScope())
  {
    // 書き込み

    ts.Complete();
  }
}

void WriteChild2() {
  using (var ts = new TransactionScope())
  {
    // 書き込み

    ts.Complete();
  }
}

ここで、子の TransactionScope が Complete されずに Dispose されるとその時点でロールバックされるので、親で Complete しようとすると TransactionAbortedException が発生してしまう。 例えば子で以下のように「書き込みが発生するのは特定の場合だけだから、そうじゃなかったら Complete 呼ばない」とかすると NG。

  using (var ts = new TransactionScope())
  {
    if(shouldUpdate) 
    {
      // 書き込み

      ts.Complete();
    }
  }

(TransactionScope はコンストラクターで新規に作るか既存を流用するを指定できる (TransactionScopeOption) 。このへんの指定で挙動が変わるかも?)

参考

トランザクション スコープを使用した暗黙的なトランザクションの実装