【実務・中級編】VB.NETとトランザクション制御:TransactionScopeを用いた複数テーブル・複数DB間の安全な一括更新とロールバック – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

なぜあなたのVB.NETは「データ不整合」を起こすのか? TransactionScopeを極める

業務アプリケーション開発において、最も許されない罪は「半端なデータ」を残すことだ。
Aテーブルにはデータが入ったが、Bテーブルへの書き込みでエラーが発生し、再起動したら整合性が崩壊している……そんな「地獄のデバッグ」を経験したことはないだろうか。

VB.NETで堅牢なツールを作るなら、`SqlTransaction`をコネクションごとに手動で管理するような泥臭い実装からは卒業せよ。今回は、複数テーブル・複数DBさえも一つの「不可分な塊」として扱う、`System.Transactions.TransactionScope`による究極のトランザクション制御を伝授する。

—

1. なぜ「手動ロールバック」は悪手なのか

多くの初心者は `Try…Catch` の中で `conn.BeginTransaction()` を呼び出し、`Catch` ブロックで `trans.Rollback()` を書く。これは間違いではないが、以下の点で脆弱だ。

  • スコープの複雑化: 処理がメソッドを跨ぐと、トランザクションオブジェクトを引数で回す羽目になる。
  • 例外の見落とし: 複雑なロジックの中で、Rollbackを呼び忘れるパスが必ず生まれる。
  • 分散トランザクションへの非対応: 複数のデータベースをまたぐ更新が発生した瞬間、この設計は崩壊する。

`TransactionScope`は、「Usingブロックを抜けるまでに完了(Complete)が宣言されなければ、自動的にロールバックする」という「自動防御」の仕組みだ。これがコードの保守性を劇的に高める。

—

2. 実装の鉄則:プロダクションコード

以下のコードは、単なるサンプルではない。現場で即戦力として使える、堅牢性を担保したテンプレートだ。

Imports System.Transactions ‘ 必須参照: System.Transactions.dll

Public Sub ExecuteBusinessTransaction()
‘ TransactionScopeOption.Required: 既存のトランザクションがあれば参加し、なければ新規作成
‘ TransactionOptions: タイムアウト設定などを制御可能
Using scope As New TransactionScope(TransactionScopeOption.Required)
Try
‘ 1. メインデータの更新
UpdateMainTable()

‘ 2. ログテーブルや外部DBへの書き込み
UpdateSubTable()

‘ すべての処理が正常終了した場合のみ、Completeを呼ぶ
‘ これを忘れると、どんなに処理が成功しても自動的にロールバックされる
scope.Complete()

Catch ex As Exception
‘ ここでロールバックを明示的に書く必要はない
‘ scope.Dispose() が呼ばれた時点で、Complete()が未実行なら自動ロールバックされる
Logger.Error(“トランザクション失敗: ” & ex.Message)
Throw ‘ 上位レイヤーへ例外を再スローし、異常系であることを伝播させる
End Try
End Using
End Sub

—

3. 現場で「ハマる」ポイントと回避策

伝説のアーキテクトとして、一つ忠告しておく。`TransactionScope`を使う際に陥りやすい罠が2つある。

① 「接続文字列」の罠

`TransactionScope`は、同一の接続文字列であれば自動的に「接続の共有」を行う。しかし、異なる接続文字列(別のDBサーバーなど)を同時に開くと、自動的に「分散トランザクション(MSDTC)」へ昇格する。

  • 対策: MSDTCが有効になっていない環境ではエラーになる。極力、一つのDBに処理を閉じる設計にするか、MSDTCのサービス設定を事前に確認せよ。

② 非同期処理との相性

.NET Framework 4.5.1以前や、古いライブラリを使っている場合、`Async/Await`と`TransactionScope`を組み合わせるとトランザクションが別スレッドに引き継がれず、死ぬほどハマる。

  • 対策: 非同期処理を伴う場合は、必ず `TransactionScopeAsyncFlowOption.Enabled` を指定すること。

Using scope As New TransactionScope(TransactionScopeOption.Required,
New TransactionOptions With {.IsolationLevel = IsolationLevel.ReadCommitted},
TransactionScopeAsyncFlowOption.Enabled)
‘ 非同期処理…
Await db.SaveChangesAsync()
scope.Complete()
End Using

—

4. 最後に:エンジニアとしての矜持

トランザクション制御は、ビジネスの「信頼」そのものだ。
「動けばいい」コードを書くのは学生の遊びだ。プロフェッショナルは、「異常が発生した瞬間に、プログラムがどう安全に死ぬか」を設計する。

`TransactionScope`は、そのための最も強力な武器の一つだ。これを正しく使いこなし、ユーザーが安心して使える「落ちない業務ツール」を作り上げろ。

君たちのコードが、誰かの業務を救うことを願っている。何か壁にぶつかったら、またここへ戻ってくるといい。

タイトルとURLをコピーしました