C# - TransactionScope(トランザクションスコープ)の使い方
C# の トランザクションとは
ここで言う 「 トランザクション 」 とは、一連の処理単位のことです。
トランザクション内の処理は、全てが成功した時のみ確定され、処理の途中でエラーが起きた場合は、そこまでの処理をロールバックして、トランザクション処理実行前の状態まで戻ります。
データベース側でトランザクションに入れられる時は良いですが、複数のデータベースを更新する必要があったり、途中でクライアント側処理を挟む必要があったりと、データベース側ではトランザクションに入れられない時もあると思います。
C# では、TransactionScope クラスを使って、簡単にデータベースに関連する処理をコード側からトランザクションとして処理することができます。
エラーが出るとロールバックしてくれるので、データの整合性を保つのにとても便利です。
System.Transactions を [参照] に追加する
まず、TransactionScope を使いたいので、参照に System.Transactions を追加します。
ソリューションエクスプローラーで [参照] を右クリックし [参照の追加] をクリックします。
[アセンブリ] の [フレームワーク] から System.Transactions を選択し、[OK] をクリックします。
[参照] に System.Transactions が追加されました。
以下の using ディレクティブを追加しておいてください。
using System.Transactions;
TransactionScope を使ってトランザクションを実行する
違うサーバにあるデータベースとの分散トランザクションも可能ですが、設定がいろいろと必要になりますので、今回は同じ PC にインストールされた別のインスタンスのデータベースに接続して試します。
同じ PC 上にある MSSQLSERVER2017 という名前付きインスタンスと、SQLEXPRESS という名前付きインスタンス上の Test データベースに接続します。
App.config ファイルはこんな感じです。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="DB1" connectionString="Server=.\MSSQLSERVER2017;Database=Test;Uid=sa;Pwd=*******"/> <add name="DB2" connectionString="Server=.\SQLEXPRESS;Database=Test;Uid=sa;Pwd=*******" /> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> </startup> </configuration>
次のコードで、TransactionScope を使って、両方のデータベースに存在する uspTest というストアドプロシージャを実行します。
using (TransactionScope scope = new TransactionScope()) { using (SqlConnection conn1 = new SqlConnection(ConfigurationManager.ConnectionStrings["DB1"].ConnectionString)) { conn1.Open(); SqlCommand cmd1 = new SqlCommand("uspTest", conn1); cmd1.CommandType = CommandType.StoredProcedure; cmd1.Parameters.AddWithValue("ExecutedFrom", "Test1"); cmd1.ExecuteNonQuery(); using (SqlConnection conn2 = new SqlConnection(ConfigurationManager.ConnectionStrings["DB2"].ConnectionString)) { conn2.Open(); SqlCommand cmd2 = new SqlCommand("uspTest", conn2); cmd2.CommandType = CommandType.StoredProcedure; cmd2.Parameters.AddWithValue("ExecutedFrom", "Test2"); cmd2.ExecuteNonQuery(); } } scope.Complete(); }
scope.Complete(); まで到達すれば、変更が反映されますが、エラーや return などで、そこに到達する前に TransactionScope のスコープを抜けると、データベースへの変更は自動的にロールバックされます。
uspTest の内容はこんな感じで、実行されると TestResult テーブルに @ExecutedFrom と実行時間がインサートされるだけです。
このコードを実行すると、次のように両方のデータベースの uspTest が実行され、TestResult テーブルにレコードがインサートされました。
TransactionScope の Isolation レベル(トランザクション分離レベル)は指定可能ですが、 デフォルトは Serializable (直列化可能)になっています。
Serializable (直列化可能)は一番強く、安全な分離レベルです。 このトランザクションが読んだデータは、このトランザクションが終わるまで他のトランザクションが変更することができません。 また、他のトランザクションがこのトランザクションに影響するような新しいデータを挿入することもできません。
その分、一番他のトランザクションをブロックしますので、この Isolation レベルで長い処理をトランザクションとして実行しないように気をつけてくださいね!
MSDTC on server [サーバー名] is unavailable のエラーが出た場合
2 つめのデータベースの接続を Open した時に 「 MSDTC on server [サーバー名] is unavailable 」 というエラーが出た場合は、Distributed Transaction Coordinator サービスが停止している可能性があります。
検索で Services.msc と入力して Services を起動してください。
そこで Distributed Transaction Coordinator を探し、停止している場合は右クリックでサービスを開始した後で、再度実行してみてください。