このプロジェクトでは、TransactionScopeを使用して、データアクセスレイヤーがトランザクションでアクションを実行するようにします。エンドユーザーのマシンでMSDTCサービスを有効にする必要がないことを目指しています。
問題は、開発者のマシンの半分で、MSDTCを無効にして実行できることです。残りの半分は有効にする必要があります。そうしないと、「[サーバー]のMSDTCが利用できません」というエラーメッセージが表示されます。
それは本当に頭を悩ませる原因になり、ADO.NETトランザクションオブジェクトに基づく自作のTransactionScopeのようなソリューションにロールバックすることを真剣に検討するように私に促します。それは一見非常識だ-私たちの開発者の半分の作品は(とエスカレートしない)と同じコードし、他の開発者にエスカレートします。
トランザクションがDTCにエスカレートされる理由をトレースに適切に回答することを望んでいましたが、残念ながらそうではありません。
問題を引き起こすコードのサンプルは次のとおりです。エスカレートしようとするマシンでは、2番目の接続でエスカレートしようとします。
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トランザクションにエスカレートします。
- トランザクションを別のアプリケーションドメインまたは別のプロセスに「マーシャリング」する要求が呼び出されます。たとえば、アプリケーションドメインの境界を越えたトランザクションオブジェクトのシリアル化。トランザクションオブジェクトは値でマーシャリングされます。つまり、それを(同じプロセス内であっても)アプリケーションドメインの境界を越えて渡そうとすると、トランザクションオブジェクトがシリアル化されます。トランザクションオブジェクトを渡すには、トランザクションをパラメータとして取るリモートメソッドを呼び出すか、リモートのトランザクションサービスコンポーネントにアクセスしてみます。これにより、トランザクションオブジェクトがシリアル化され、アプリケーションドメイン全体でトランザクションがシリアル化される場合と同様に、エスカレーションが行われます。それは配布されており、ローカルトランザクションマネージャーはもはや適切ではありません。
#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:これらの複数のSqlConnectionがネストされている場合、つまり、2つ以上のSqlConnectionが同時に開かれている場合、TransactionScopeはすぐにDTCにエスカレートします。
- 警告#2:追加のSqlConnectionが別の「永続リソース」(つまり、別のSQL Server)に対して開かれている場合、すぐにDTCにエスカレートされます
SQL2005:
- 1つのTransactionScope期間内に複数の接続を許可しません。2番目のSqlConnectionが開かれると、エスカレートします。
アップデート4
さらに、この質問をするの関心では混乱するだけで、より明瞭のために便利な、と、ここにあなたがDTCにエスカレートする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が不要になるまでインスタンス化された時点から開いたままの何らかのグローバル接続オブジェクトが必要です。グローバル接続オブジェクトのコードの臭いに加えて、接続を最初に開いて最後に閉じることは、接続を可能な限り遅く開いて、できるだけ早く閉じるというロジックとは相容れません。