私たちのプロジェクトでは、TransactionScope を使用して、データ アクセス レイヤーがトランザクション内でそのアクションを確実に実行するようにしています。エンド ユーザーのマシンで MSDTC サービスを有効にする必要がないことを目指しています。
問題は、半分の開発者マシンで、MSDTC を無効にして実行できることです。残りの半分は有効にする必要があります。そうしないと、"[SERVER] の MSDTC は利用できません" というエラー メッセージが表示されます。
本当に頭を悩ませており、ADO.NET トランザクション オブジェクトに基づいて自作の TransactionScope のようなソリューションに戻すことを真剣に考えています。これは正気ではないように思えます - 私たちの開発者の半分で動作する (そしてエスカレートしない) 同じコードが、他の開発者ではエスカレートします。
トランザクションが DTC にエスカレートされる理由を追跡するためのより良い回答を期待していましたが、残念ながらそうではありません。
問題を引き起こすコードのサンプル ビットを次に示します。エスカレートしようとするマシンでは、2 番目の connection.Open() でエスカレートしようとします (そして、その時点で開かれている他の接続はありません)。
using (TransactionScope transactionScope = new TransactionScope() {
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open();
using (SqlDataReader reader = command.ExecuteReader()) {
// use the reader
connection.Close();
}
}
}
// Do other stuff here that may or may not involve enlisting
// in the ambient transaction
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open(); // Throws "MSDTC on [SERVER] is unavailable" on some...
// gets here on only half of the developer machines.
}
connection.Close();
}
transactionScope.Complete();
}
私たちは本当に掘り下げて、これを理解しようとしました。これが動作するマシンに関する情報は次のとおりです。
- 開発 1: Windows 7 x64 SQL2008
- 開発 2: Windows 7 x86 SQL2008
- 開発 3: Windows 7 x64
SQL2005SQL2008
動作しない開発者:
- 開発 4: Windows 7 x64、
SQL2008SQL2005 - 開発 5: Windows Vista x86、SQL2005
- 開発 6: Windows XP X86、SQL2005
- 自宅の PC : Windows Vista Home Premium、x86、SQL2005
問題を突き止めようとして、Microsoft Update から入手できるすべてのパッチがすべてのマシンに適用されていることを付け加えておきます。
更新 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/は、同様の問題について説明しています... 2006年に戻って!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - そのコード サンプルを読んでください。ネストされた 2 番目の接続 (2 番目の SQL サーバーへの、実際には) DTC にエスカレートします。 私たちのコードではこれを行っていません - 別の SQL サーバーも別の接続文字列も使用しておらず、ネストされたセカンダリ接続も開いていません - DTC へのエスカレーションがあってはなりません。
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (2005 年以降) では、SQL2000 に接続するときに DTC へのエスカレーションが常に発生する方法について説明しています。SQL2005/2008 を使用しています
- http://msdn.microsoft.com/en-us/library/ms229978.aspxトランザクション エスカレーションに関する MSDN。
MSDN のトランザクション エスカレーション ページには、次の条件によりトランザクションが DTC にエスカレートされると記載されています。
- 単一フェーズの通知をサポートしていない少なくとも 1 つの永続的なリソースが、トランザクションに登録されています。
- 単一フェーズの通知をサポートする少なくとも 2 つの永続的なリソースが、トランザクションに登録されます。たとえば、単一の接続を登録しても、トランザクションは昇格されません。ただし、データベースへの 2 番目の接続を開いてデータベースを参加させると、System.Transactions インフラストラクチャは、それがトランザクション内の 2 番目の永続的なリソースであることを検出し、MSDTC トランザクションにエスカレートします。
- トランザクションを別のアプリケーション ドメインまたは別のプロセスに「マーシャリング」する要求が呼び出されます。たとえば、アプリケーション ドメインの境界を越えたトランザクション オブジェクトのシリアル化です。トランザクション オブジェクトは値によってマーシャリングされます。つまり、アプリケーション ドメインの境界を越えて渡そうとすると (同じプロセスであっても)、トランザクション オブジェクトがシリアル化されます。Transaction をパラメーターとして受け取るリモート メソッドで呼び出しを行うことによって、トランザクション オブジェクトを渡すことができます。または、リモート トランザクション サービス コンポーネントへのアクセスを試みることができます。これにより、トランザクション オブジェクトがシリアル化され、アプリケーション ドメイン全体でトランザクションがシリアル化される場合と同様に、エスカレーションが発生します。これは分散されており、ローカル トランザクション マネージャーはもはや適切ではありません。
私たちは#3を経験していません。#2は、一度に1つの接続しかなく、単一の「永続的なリソース」に対しても接続されるため、発生していません。#1が起こる方法はありますか?単一フェーズ通知をサポートしない原因となる SQL2005/8 構成はありますか?
更新 2:
個人的に、全員の SQL Server バージョンを再調査しました。「Dev 3」には実際には SQL2008 があり、「Dev 4」には実際には SQL2005 があります。同僚を二度と信用しないように教えてくれます。;) このデータの変更により、問題が見つかったと確信しています。SQL2008 には、SQL2005 にはない大量の素晴らしい機能が含まれているため、SQL2008 の開発者はこの問題に直面していませんでした。
また、SQL2005 をサポートする予定であるため、これまでのように TransactionScope を使用することはできず、TransactionScope を使用したい場合は、単一の SqlConnection オブジェクトを渡す必要があることもわかります...これは、SqlConnection を簡単に渡すことができない状況では問題があるようです...グローバル SqlConnection インスタンスのにおいがするだけです。ピュー!
アップデート 3
ここで質問を明確にするために:
SQL2008:
- 1 つの TransactionScope 内で複数の接続を許可します (上記のサンプル コードで示されているように)。
- 警告 #1: これらの複数の SqlConnections がネストされている場合、つまり、2 つ以上の SqlConnections が同時に開かれている場合、TransactionScope はすぐに DTC にエスカレートします。
- 警告 #2: 追加の SqlConnection が別の「永続的なリソース」 (つまり、別の SQL Server) に対して開かれると、すぐに DTC にエスカレートされます。
SQL2005:
- 1 つの TransactionScope 期間内に複数の接続を許可しません。2 番目の SqlConnection が開かれるとエスカレートします。
更新 4
この質問をさらに便利にするために、そしてより明確にするために、SQL2005を単一の SqlConnection
.
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
SqlConnection.Open()
これは私には壊れているように見えますが、へのすべての呼び出しが接続プールから取得されている かどうかは理解できると思います。
「しかし、なぜこれが起こるのでしょうか?」 接続が開かれる前にその接続に対して SqlTableAdapter を使用すると、SqlTableAdapter は接続を開いたり閉じたりして、トランザクションを効果的に終了します。
したがって、基本的に、SQL2005 で TransactionScope を正常に使用するには、最初の TransactionScope がインスタンス化されてから不要になるまで、何らかのグローバル接続オブジェクトを開いたままにしておく必要があります。グローバル接続オブジェクトのコード臭に加えて、最初に接続を開いて最後に閉じることは、接続をできるだけ遅く開き、できるだけ早く閉じるというロジックに反します。