web-dev-qa-db-ja.com

TransactionScope:分散トランザクションの回避

とりわけ、子オブジェクトのコレクション(List<t>)を含む親オブジェクト(DALの一部)があります。

オブジェクトをDBに保存し直すときは、親を入力/更新してから、各子をループします。保守性のために、子のすべてのコードを別のプライベートメソッドに入れました。

標準のADOトランザクションを使用するつもりでしたが、旅行中に、TransactionScopeオブジェクトに遭遇しました。これにより、すべてのDBインタラクションを(すべてのインタラクションとともに)親メソッドでラップできるようになると思います。子メソッドで)1つのトランザクションで。

ここまでは順調ですね..?

したがって、次の質問は、このTransactionScope内で接続を作成して使用する方法です。複数の接続を使用すると、それらが同じDBに接続されている場合でも、TransactionScopeが分散トランザクションであると見なす可能性があると聞いています(高価なDTC作業が含まれます)。

本当ですか?それとも、他の場所で読んでいるように、同じ接続文字列(接続プールに役立つ)を使用しても問題ない場合はありますか?

もっと実際的に言えば、私は...

  1. 親と子に別々の接続を作成します(同じ接続文字列を使用しますが)
  2. 親に接続を作成し、それをパラメーターとして渡します(私には不器用なようです)
  3. 他に何かする...?

更新:

通常の.NET3.5 +とSQLServer 2008+を使用しても問題ないように見えますが、このプロジェクトの別の部分ではOracle(10g)を使用するため、テクニックを練習したほうがよいでしょう。プロジェクト間で一貫して使用できます。

したがって、接続を子メソッドに渡すだけです。


オプション1コードサンプル:

using (TransactionScope ts = new TransactionScope())
            {
                using (SqlConnection conn = new SqlConnection(connString))
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.Connection.Open();
                        cmd.CommandType = CommandType.StoredProcedure;

                        try
                        {
                            //create & add parameters to command

                            //save parent object to DB
                            cmd.ExecuteNonQuery();

                            if ((int)cmd.Parameters["@Result"].Value != 0)
                            {
                                //not ok
                                //rollback transaction
                                ts.Dispose();
                                return false;
                            }
                            else //enquiry saved OK
                            {
                                if (update)
                                {
                                    enquiryID = (int)cmd.Parameters["@EnquiryID"].Value;
                                }

                                //Save Vehicles (child objects)
                                if (SaveVehiclesToEPE())
                                {
                                    ts.Complete();
                                    return true;
                                }
                                else
                                {
                                    ts.Dispose();
                                    return false;
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            //log error
                            ts.Dispose();
                            throw;
                        }
                    }
                }
            }
19
CJM

多くのデータベースADOプロバイダー(Oracle ODP.NETなど)は、TransactionScopeを使用して複数の接続間でトランザクションを実行すると、同じ接続文字列を共有している場合でも、実際に分散トランザクションを開始します。

一部のプロバイダー(.NET 3.5以降のSQL2008など)は、同じ接続文字列を参照するトランザクションスコープで新しい接続が作成されたときに認識し、DTCが機能しません。ただし、接続文字列の差異(チューニングパラメータなど)により、これが発生しなくなる可能性があります。動作は分散トランザクションの使用に戻ります。

残念ながら、分散トランザクションを作成せずにトランザクションが連携して機能することを保証する唯一の信頼できる手段は、同じトランザクションを「続行」する必要があるメソッドに接続オブジェクト(またはIDbTransaction)を渡すことです。

作業を行っているクラスのメンバーへの接続を高めるのに役立つ場合もありますが、これは厄介な状況を生み出す可能性があり、接続オブジェクトの存続期間と破棄の制御を複雑にします(通常、usingステートメントの使用が妨げられるため) )。

24
LBushkin

経験的に、(SQL Serverプロバイダーの場合)ifプロセスは接続プールを利用して、親プロセスと子プロセスの間で接続(およびトランザクション)を共有できると判断しましたが、DTCは必ずしも巻き込まれる。

これは大きな「if」ですが、例のように、親プロセスによって作成された接続を子プロセスで共有することはできません(子プロセスを呼び出す前に接続を閉じたり解放したりしないでください)。これにより、2つの実際の接続にまたがるトランザクションが発生し、トランザクションが分散トランザクションにプロモートされます。

このシナリオを回避するには、コードをリファクタリングするのは簡単なようです。子プロセスを呼び出す前に、親プロセスによって作成された接続を閉じるだけです。

2
Daniel Pratt

あなたの例では、TransactionScopeはまだメソッドのコンテキストにあり、その下に複数のコマンドを使用してSqlTransactionを作成するだけで済みます。トランザクションをメソッドの外に移動する場合、たとえば、そのメソッドの呼び出し元にする場合、または複数のデータベースにアクセスする場合は、TransactionScopeを使用します。

更新:子の呼び出しを見つけてもかまいません。この状況では、接続オブジェクトを子クラスに渡すことができます。また、TransactionScopeを手動で破棄する必要はありません。ブロックを使用すると、try-finallyブロックのように機能し、例外が発生した場合でも破棄が実行されます。

更新2:さらに良いことに、IDbTransactionを子クラスに渡します。そこから接続を取得できます。

1