ASP.NETでのトランザクション制御
トランザクション制御の概念が分かったところで、今度は実際どのようにしてトランザクション制御を行うか説明していきましょう。
なお、トランザクション制御がからむとSQL Server Compact 4.0では実際のSQL Serverと挙動が異なる部分がありますので、次のようにWeb.configを書き換え、SQL Server Expressを使用するように変更します。
<connectionStrings> <add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient" /> <!-- 変更前 SQL Server Compact <add name="MRRSConnectionString" connectionString="Data Source=|DataDirectory|\MRRS.sdf" providerName="System.Data.SqlServerCe.4.0" /> --> <!-- 変更後 SQL Server Express --> <add name="MRRSConnectionString" connectionString="data source=.\SQLEXPRESS;Integrated Security=true;AttachDBFilename=|DataDirectory|\MRRS.mdf;User Instance=true;Initial Catalog=MRRS;" providerName="System.Data.SqlClient"/> </connectionStrings>
トランザクション制御方法の種類
ASP.NETをはじめとした.NETアプリケーションでトランザクション制御を行うには、主に次の2つの方法があります。
- 明示的トランザクション制御
- 暗黙的トランザクション制御
1. 明示的トランザクション制御
.NET Framework 1.0の頃からある方法で、ADO.NETの機能を使い開発者自らがトランザクションの開始、終了をはじめとした挙動を制御する方法です。
明示的トランザクション制御の実装コード例を以下に示します。
/// <summary>
/// 予約情報を追加します。
/// </summary>
/// <param name="reservation"></param>
public void Insert(Reservation reservation)
{
using (var context = new MRRSContext())
{
var reservationRepository = new ReservationRepository(context);
var reservationHistoryRepository = new ReservationHistoryRepository(context);
var conn = ((IObjectContextAdapter)context).ObjectContext.Connection; // (1)
conn.Open(); // (2)
using (var tran = conn.BeginTransaction()) // (3)
{
try
{
if (OverlapsOtherReservation(reservationRepository, reservation))
{
// 重複した予約あり
throw new OverlapReservationException(); // (6)
}
// 予約登録
reservationRepository.Insert(reservation);
reservationRepository.Save();
// 予約履歴登録
RegisterHistory(reservationHistoryRepository, reservation, Operation.Insert);
tran.Commit(); // (4)
}
catch
{
tran.Rollback(); // (5)
throw;
}
}
conn.Close(); // (7)
}
}
(1)IDbConnectionオブジェクトを取得する。EFを用いる場合、context.Database.ConnectionプロパティでもIDbConnectionオブジェクトを取得することもできますが、今回のサンプルのように自分でIDbConnection.Openメソッドを呼び出すと、予約登録の箇所で例外が発生してしまいます。これを回避するため、上記のようなコードでIDbConnectionオブジェクトを取得する必要があります(参考:Exception from DbContext API: EntityConnection can only be constructed with a closed DbConnection)。
(2)IDbConnection.Openメソッドを呼び出し、DB接続を開く。この後の(3)でトランザクションを開始するには、DB接続が開いていないといけないため、ここでOpenメソッドを呼び出す必要があります。
(3)IDbConnection.BeginTransactionメソッドにより、トランザクションを開始する。
(4)処理が正常に終わったら、IDbTransaction.Commitメソッドによりトランザクションをコミットする。
(5)システムエラーが発生したら、IDbTransaction.Rollbackメソッドによりトランザクションをロールバックする。
(6)システムエラーに限らず、業務エラー発生時も同様にロールバックし、処理を中断する。
(7)IDbConnection.Closeメソッドを呼び出し、DB接続を閉じる。
後述する暗黙的トランザクション制御に比べれば多少処理が煩雑にはなりますが、開発者が完全に制御することが可能なので、バッチ処理など複雑なトランザクション制御が必要な際にはまだまだ現役で使われています。
ただ、複数のデータベース接続に対する処理を1つのトランザクションとする「分散トランザクション」には対応していません。分散トランザクションが必要な場合は暗黙的トランザクション制御を使うようにしましょう。
暗黙的トランザクション制御
.NET Framework 2.0(ADO.NET 2.0)から登場した方法で、現在は大半のケースでこちらを使用することを推奨します。
暗黙的トランザクション制御の実装コード例を以下に示します。
/// <summary>
/// 予約情報を更新します。
/// </summary>
/// <param name="reservation"></param>
public void Update(Reservation reservation)
{
using (var ts = new TransactionScope()) // (1)
{
if (OverlapsOtherReservation(_reservationRepository, reservation))
{
// 重複した予約あり
throw new OverlapReservationException(); // (3)
}
_reservationRepository.Update(reservation);
_reservationRepository.Save();
// 履歴登録
RegisterHistory(_reservationHistoryRepository, reservation, Operation.Update);
ts.Complete(); // (2)
}
}
(1)usingブロックを使用し、System.Transaction.TransactionScopeクラスのインスタンスを生成する。usingブロックの内部が1つのトランザクションとして扱われます。
(2)処理が正常に終わったらTransaction.Completeメソッドを呼び出す。Completeメソッドを呼び出すことで、このトランザクションが完了したというマークが付けられます。その後、usingブロックを抜ける際にTransactionScope.Disposeメソッドが自動的に呼び出され、その中でコミット処理が行われます。
(3)システムエラーや業務エラー発生時はTransactionScope.Completeメソッドが呼び出されずにusingブロックを抜ける。Completeメソッドが呼び出されないため、トランザクションが完了したというマークが付けられません。そのときはDisposeメソッドの中でロールバック処理が行われます。
暗黙的トランザクション制御のほうが明示的トランザクション制御に比べ大幅にコードが簡略化されて見通しがよくなっていることが分かると思います。また、IDbConnectionオブジェクトとの関連付けなどの細々とした処理がなくなっていることも分かると思います。このようにデータアクセステクノロジに特化した処理が不要なことが、暗黙的トランザクション制御の大きな利点の一つです。
さらに、暗黙的トランザクション制御は前述のように、複数のデータベース接続にまたがる「分散トランザクション」に対応しています。最近は作成するアプリケーションの規模によっては、データベースが冗長化されていることも珍しくありません。そんな時でも、暗黙的トランザクション制御を用いることで、単一トランザクション、分散トランザクションにかかわらず同じコードがそのまま利用できます。このことは、習得容易性や可読性の面からも好ましいです。
