web-dev-qa-db-ja.com

どのような状況下で、SqlConnectionはアンビエントTransactionScopeトランザクションに自動的に登録されますか?

SqlConnectionがトランザクションに「参加する」とはどういう意味ですか?接続で実行するコマンドがトランザクションに参加するということですか?

もしそうなら、どのような状況でSqlConnection 自動的にアンビエントTransactionScopeトランザクションに参加しますか?

コードコメントの質問を参照してください。各質問の答えに対する私の推測は、括弧内の各質問に続きます。

シナリオ1:トランザクションスコープ内で接続を開く

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{   
    // Q1: Is connection automatically enlisted in transaction? (Yes?)
    //
    // Q2: If I open (and run commands on) a second connection now,
    // with an identical connection string,
    // what, if any, is the relationship of this second connection to the first?
    //
    // Q3: Will this second connection's automatic enlistment
    // in the current transaction scope cause the transaction to be
    // escalated to a distributed transaction? (Yes?)
}

シナリオ2:外部で開かれたトランザクションスコープ内で接続を使用する

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
    // Connection was opened before transaction scope was created
    // Q4: If I start executing commands on the connection now,
    // will it automatically become enlisted in the current transaction scope? (No?)
    //
    // Q5: If not enlisted, will commands I execute on the connection now
    // participate in the ambient transaction? (No?)
    //
    // Q6: If commands on this connection are
    // not participating in the current transaction, will they be committed
    // even if rollback the current transaction scope? (Yes?)
    //
    // If my thoughts are correct, all of the above is disturbing,
    // because it would look like I'm executing commands
    // in a transaction scope, when in fact I'm not at all, 
    // until I do the following...
    //
    // Now enlisting existing connection in current transaction
    conn.EnlistTransaction( Transaction.Current );
    //
    // Q7: Does the above method explicitly enlist the pre-existing connection
    // in the current ambient transaction, so that commands I
    // execute on the connection now participate in the
    // ambient transaction? (Yes?)
    //
    // Q8: If the existing connection was already enlisted in a transaction
    // when I called the above method, what would happen?  Might an error be thrown? (Probably?)
    //
    // Q9: If the existing connection was already enlisted in a transaction
    // and I did NOT call the above method to enlist it, would any commands
    // I execute on it participate in it's existing transaction rather than
    // the current transaction scope. (Yes?)
}
198
Triynko

私はこの質問をしてからいくつかのテストを行ったが、誰も答えなかったので、すべてではないとしてもほとんどの答えを自分で見つけた。私が何かを見逃したかどうか教えてください。

Q1。はい、接続文字列で「enlist = false」が指定されていない限り。接続プールは使用可能な接続を見つけます。使用可能な接続とは、トランザクションに参加していない接続または同じトランザクションに参加している接続です。

Q2。2番目の接続は独立した接続であり、同じトランザクションに参加します。これらの2つの接続は同じデータベースに対して実行されているため、コマンドの相互作用についてはわかりませんが、コマンドが両方で同時に発行されるとエラーが発生する可能性があると思います: "Transactionのようなエラー別のセッションで使用中のコンテキスト」

Q3。はい、分散トランザクションにエスカレートされるため、同じ接続文字列であっても複数の接続を登録すると、分散になりますTransaction.Current.TransactionInformation.DistributedIdentifierでnull以外のGUIDをチェックすることで確認できます。同じ接続文字列が両方の接続に使用されている場合は使用されません(両方の接続が同時に開いていない限り)。これにより、トランザクション内で接続を開いて複数回閉じることができます。接続をできる限り遅く開き、できるだけ早く閉じることにより、接続プール。

Q4。いいえ。アクティブなトランザクションスコープがないときに開かれた接続は、新しく作成されたトランザクションスコープに自動的に登録されません。

Q5。いいえ。トランザクションスコープで接続を開くか、既存の接続をスコープに登録しない限り、基本的にトランザクションはありません。コマンドをトランザクションに参加させるには、接続をトランザクションスコープに自動または手動で登録する必要があります。

Q6。はい、コードがトランザクションスコープブロックで実行された場合でも、トランザクションに参加していない接続のコマンドは発行されたままコミットされますロールバックされました。接続が現在のトランザクションスコープに参加していない場合、トランザクションに参加していないため、トランザクションをコミットまたはロールバックしても、トランザクションスコープに参加していない接続で発行されたコマンドには影響しません... as この男が見つけた 。これは、自動登録プロセスを理解しない限り、見つけるのが非常に困難です。接続が開かれたときにのみ発生しますinsideアクティブなトランザクションスコープ。

Q7。はい。 EnlistTransaction(Transaction.Current)を呼び出すことにより、既存の接続を現在のトランザクションスコープに明示的に登録できます。また、DependentTransactionを使用して、トランザクション内の別のスレッドに接続を参加させることもできますが、以前と同じように、同じデータベースに対する同じトランザクションに関係する2つの接続がどのように相互作用するかはわかりません...もちろん、2番目の参加接続により、トランザクションは分散トランザクションにエスカレートします。

Q8。エラーがスローされる場合があります。 TransactionScopeOption.Requiredが使用され、接続が既にトランザクションスコープトランザクションに参加している場合、エラーはありません。実際、スコープに対して作成された新しいトランザクションはなく、トランザクション数(@@ trancount)は増加しません。ただし、TransactionScopeOption.RequiresNewを使用すると、新しいトランザクションスコープトランザクションに接続を登録しようとすると、「接続には現在トランザクションが登録されています。現在のトランザクションを終了して、再試行してください」というエラーメッセージが表示されますそして、はい、接続が参加しているトランザクションを完了すると、新しいトランザクションに安全に接続を参加できます。 更新:以前に接続でBeginTransactionを呼び出した場合、新しいトランザクションスコープトランザクションに参加しようとすると、少し異なるエラーがスローされます。 "接続でローカルトランザクションが進行中のため、トランザクションに参加できません。ローカルトランザクションを終了して、再試行してください。」一方、トランザクションスコープトランザクションに参加している間にSqlConnectionでBeginTransactionを安全に呼び出すことができ、ネストされたトランザクションスコープのRequiredオプションを使用するのとは異なり、実際には@@ trancountを1つ増やします増加する。興味深いことに、その後、必須オプションを使用して別のネストされたトランザクションスコープを作成する場合、既にアクティブなトランザクションスコープトランザクションを持っている結果として何も変化しないため、エラーは発生しません(トランザクションが@@ trancountスコープトランザクションは既にアクティブであり、必須オプションが使用されています。

Q9。はい。コマンドは、C#コードでアクティブなトランザクションスコープが何であるかに関係なく、接続が参加しているトランザクションに参加します。

182
Triynko

素晴らしい仕事トリインコ、あなたの答えはすべて私にとって非常に正確で完全に見えます。私が指摘したい他のいくつかのこと:

(1)手動登録

上記のコードでは、次のような手動登録を(正しく)示しています。

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

ただし、接続文字列でEnlist = falseを使用して、このようにすることもできます。

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
    using (SqlConnection conn1 = new SqlConnection(connStr))
    {
        conn1.Open();
        conn1.EnlistTransaction(Transaction.Current);
    }

    using (SqlConnection conn2 = new SqlConnection(connStr))
    {
        conn2.Open();
        conn2.EnlistTransaction(Transaction.Current);
    }
}

ここで注意すべきことがもう1つあります。 conn2が開かれると、接続プールコードは、conn1と同じトランザクションに後で登録することを知りません。つまり、conn2にはconn1とは異なる内部接続が与えられます。次に、conn2が参加すると、2つの接続が参加するため、トランザクションをMSDTCに昇格する必要があります。この昇格は、自動登録を使用することによってのみ回避できます。

(2).Net 4.0より前は、接続文字列で "Transaction Binding = Explicit Unbind" を設定することを強くお勧めします。この問題は.Net 4.0で修正され、Explicit Unbindがまったく不要になりました。

(3)独自のCommittableTransactionをローリングし、それにTransaction.Currentを設定することは、基本的にTransactionScopeします。これは実際にはめったに役に立ちません。参考までに。

(4)Transaction.Currentはスレッド静的です。これは、Transaction.CurrentTransactionScopeを作成したスレッドでのみ設定されることを意味します。したがって、複数のスレッドが同じTransactionScopeを実行する(おそらくTaskを使用する)ことはできません。

19
Jared Moore

私たちが見たもう1つの奇妙な状況は、EntityConnectionStringBuilderを構築すると、_TransactionScope.Current_でマックし、トランザクションに参加する(と思う)ことです。デバッガーでこれを確認しました。_TransactionScope.Current_の_current.TransactionInformation.internalTransaction_は、構築する前に_enlistmentCount == 1_を示し、その後_enlistmentCount == 2_を示します。

これを回避するには、内部に構築します

using (new TransactionScope(TransactionScopeOption.Suppress))

場合によっては操作の範囲外です(接続が必要になるたびに構築していました)。

0
Todd