using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SQLite;
using System.Transactions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:")) // SQLiteはデフォルトで自動コミットモード
{
cnn.Open(); // この時点でトランザクションスコープに含まれる必要がある
using (TransactionScope tran = new TransactionScope())
{
try
{
using (SQLiteCommand cmd = cnn.CreateCommand())
{
cmd.CommandText = "CREATE TABLE EMPLOYEES (ID INTEGER PRIMARY KEY, NAME NVARCHAR(256))";
cmd.ExecuteNonQuery();
cmd.CommandText = "INSERT INTO EMPLOYEES (NAME) VALUES('HOGE')";
cmd.ExecuteNonQuery();
throw new Exception("ロールバックしたい!");
}
tran.Complete(); // 到達しない
}
catch { }
}
using (SQLiteCommand cmd = cnn.CreateCommand())
{
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEES";
SQLiteDataAdapter adapter = new SQLiteDataAdapter(cmd);
DataSet ds = new DataSet();
adapter.Fill(ds);
Console.WriteLine(ds.Tables[0].Rows[0][0]); // 1 (ロールバックされていない)
}
}
Console.ReadKey();
}
}
}
上記のコードでは例外でTransactionScopeを抜けたにも関わらず更新がロールバックされません。
TransactionScopeのusingは一見トランザクションを開始しているように見えますが、このコードを実際にプロファイルで確認するとトランザクションは開始されていません。
実際にトランザクションの対象となるのはConnectionであり、TransactionScope内のConnection.Open()がトランザクションに参加するようです。
ですのでサンプルのコードはConnection.Open()がTransactionScopeの外側にあるためトランザクションに参加しておらず、自動コミットモードで更新が行われているためロールバックされないのです。
なのでこれはNGで
using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:"))
{
cnn.Open(); // NG
using (TransactionScope tran = new TransactionScope())
{
tran.Complete();
}
}
これはOK
using (TransactionScope tran = new TransactionScope())
using (SQLiteConnection cnn = new SQLiteConnection("Data Source=:memory:"))
{
cnn.Open(); // OK
tran.Complete();
}
教訓:ConnectionはTransactionScopeの内側に書く(定型文!)