質問
C# .Net 2.0 でトランザクションを実行するためのベスト プラクティスは何ですか。使用すべきクラスは何ですか?注意すべき落とし穴は何ですか?などコミットとロールバックのすべて。DB にデータを挿入する際にいくつかのトランザクションを実行する必要がある可能性があるプロジェクトを開始したところです。取引に関する基本的な内容でも、返信やリンクは大歓迎です。
解決
トランザクションには主に 2 種類あります。接続トランザクションとアンビエント トランザクション。接続トランザクション (SqlTransaction など) は DB 接続 (SqlConnection など) に直接関連付けられています。つまり、接続を渡し続ける必要があります。場合によっては問題ありませんが、「作成/使用/解放」は許可されません。使用法が異なり、データベース間の作業は許可されません。例 (スペース用にフォーマット):
using (IDbTransaction tran = conn.BeginTransaction()) {
try {
// your code
tran.Commit();
} catch {
tran.Rollback();
throw;
}
}
それほど乱雑ではありませんが、接続「conn」に限定されています。別のメソッドを呼び出したい場合は、「conn」を渡す必要があります。
代替手段はアンビエント トランザクションです。.NET 2.0 の新機能、 トランザクションスコープ オブジェクト (System.Transactions.dll) を使用すると、さまざまな操作での使用が可能になります (適切なプロバイダーがアンビエント トランザクションに自動的に参加します)。これにより、既存の (非トランザクション) コードへのレトロフィットや、複数のプロバイダーとの通信が容易になります (ただし、複数のプロバイダーと通信すると DTC が関与します)。
例えば:
using(TransactionScope tran = new TransactionScope()) {
CallAMethodThatDoesSomeWork();
CallAMethodThatDoesSomeMoreWork();
tran.Complete();
}
ここで、2 つのメソッドは独自の接続 (open/use/close/dispose) を処理できますが、何も渡さなくてもサイレントにアンビエント トランザクションの一部になることに注意してください。
コードにエラーがある場合は、Complete() を使用せずに Dispose() が呼び出されるため、ロールバックされます。予想されるネストなどはサポートされていますが、外部トランザクションを完了している間に内部トランザクションをロールバックすることはできません。誰かが不満を抱いている場合、トランザクションは中止されます。
TransactionScope のもう 1 つの利点は、データベースだけに結び付けられていないことです。トランザクション対応のプロバイダーであれば、これを使用できます。たとえば、WCF。あるいは、TransactionScope と互換性のあるオブジェクト モデルもいくつかあります (つまり、ロールバック機能を備えた .NET クラス - おそらく記念品よりも簡単ですが、私自身はこのアプローチを使用したことがありません)。
全体として、非常に非常に便利なオブジェクトです。
いくつかの注意事項:
- SQL Server 2000 では、TransactionScope は直ちに DTC に移行します。これは SQL Server 2005 以降で修正されており、DTC に昇格すると、2 つのソースなどと通信するまで LTM (オーバーヘッドがかなり少なくなります) を使用できます。
- があります グリッチ つまり、接続文字列を調整する必要がある可能性があります
他のヒント
protected void Button1_Click(object sender, EventArgs e)
{
using (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True"))
{
connection1.Open();
// Start a local transaction.
SqlTransaction sqlTran = connection1.BeginTransaction();
// Enlist a command in the current transaction.
SqlCommand command = connection1.CreateCommand();
command.Transaction = sqlTran;
try
{
// Execute two separate commands.
command.CommandText =
"insert into [doctor](drname,drspecialization,drday) values ('a','b','c')";
command.ExecuteNonQuery();
command.CommandText =
"insert into [doctor](drname,drspecialization,drday) values ('x','y','z')";
command.ExecuteNonQuery();
// Commit the transaction.
sqlTran.Commit();
Label3.Text = "Both records were written to database.";
}
catch (Exception ex)
{
// Handle the exception if the transaction fails to commit.
Label4.Text = ex.Message;
try
{
// Attempt to roll back the transaction.
sqlTran.Rollback();
}
catch (Exception exRollback)
{
// Throws an InvalidOperationException if the connection
// is closed or the transaction has already been rolled
// back on the server.
Label5.Text = exRollback.Message;
}
}
}
}
C#自体でトランザクションを実行する代わりに、トランザクションを独自のストアドプロシージャにラップし、そのように処理することもできます。
db関連のものだけに必要な場合、一部のORマッパー(NHibernateなど)はデフォルトですぐに使用できるtransactinoをサポートします。
必要なものにも依存します。基本的なSQLトランザクションの場合は、コードでBEGIN TRANSおよびCOMMIT TRANSを使用してTSQLトランザクションを実行できます。これが最も簡単な方法ですが、複雑であり、適切にコミット(およびロールバック)するように注意する必要があります。
次のようなものを使用します
SQLTransaction trans = null;
using(trans = new SqlTransaction)
{
...
Do SQL stuff here passing my trans into my various SQL executers
...
trans.Commit // May not be quite right
}
失敗すると、 using
からすぐにポップされ、トランザクションは常にコミットまたはロールバックします(実行するよう指示した内容によって異なります)。私たちが直面した最大の問題は、常にコミットすることでした。を使用すると、トランザクションの範囲が制限されます。