У нашому проекті ми використовуємо TransactionScope, щоб переконатися, що рівень доступу до даних виконує дії в транзакції. Ми прагнемо не вимагати включення послуги MSDTC на наших машинах кінцевого користувача.
Проблема полягає в тому, що на половині наших машин для розробників ми можемо працювати з відключеною MSDTC. У другій половині повинно бути включено або вони отримують повідомлення про помилку "MSDTC на [SERVER] недоступний" .
По-справжньому, я чухаю голову, і я серйозно розглядаю можливість повернення до домашнього розгорнутого рішення TransactionScope, заснованого на об'єктах транзакцій ADO.NET. Це , здавалося б , з розумом - той же код , який працює (і не нагнітати) на половину нашого розробника робить нагнітати на іншому розробник.
Я сподівався на кращу відповідь Trace, чому транзакція ескальована до DTC, але, на жаль, це не відбувається.
Ось зразковий біт коду, який спричинить неполадки, на машинах, які намагаються ескалацію, він намагається ескалації на другому з'єднанні. Відкрийте () (і так, немає іншого відкритого підключення на той час.)
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();
}
Ми справді копалися і намагалися розібратися в цьому. Ось деяка інформація про машини, над якими працює:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Розробники, над якими він не працює:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- Мій домашній ПК: Windows Vista Home Premium, x86, SQL2005
Варто додати, що всі машини, прагнучи вирішити проблему, були повністю виправлені з усім, що доступно в Microsoft Update.
Оновлення 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ описує подібну проблему ... ще в 2006 році!
- http://msdn.microsoft.com/en-us/library/system.transaction.transactioncope%28VS.80%29.aspx - прочитавши цей зразок коду, він чітко демонструє вкладене друге з'єднання (до другого сервера SQL, фактично), що перейде до DTC. Ми цього не робимо в нашому коді - ми не використовуємо різні SQL-сервери, ані різні рядки з'єднання, а також не вкладаємо вторинні з'єднання - не повинно бути ескалації до DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (з 2005 р.) розповідає про те, як ескалація до DTC завжди відбуватиметься під час підключення до SQL2000. Ми використовуємо SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN про ескалацію транзакцій.
На цій сторінці ескалації транзакцій MSDN зазначено, що наступні умови призведуть до ескалації транзакції до DTC:
- Принаймні один довговічний ресурс, який не підтримує однофазні повідомлення, зарахований до трансакції.
- Принаймні два довговічні ресурси, що підтримують однофазні повідомлення, зараховані до трансакції. Наприклад, зарахування єдиного з'єднання з не спричиняє просування транзакції. Однак щоразу, коли ви відкриваєте друге підключення до бази даних, що призводить до занесення бази даних, інфраструктура System.Transaction виявляє, що це другий довговічний ресурс транзакції, і передає його до транзакції MSDTC.
- Викликається запит на "маршалка" транзакції на інший домен програми або інший процес. Наприклад, серіалізація об'єкта транзакції через межу домену програми. Об'єкт транзакції порівнюється за значенням, тобто будь-яка спроба передати його через межу домену програми (навіть у тому самому процесі) призводить до серіалізації об'єкта транзакції. Ви можете передати об'єкти транзакцій, здійснивши виклик за віддаленим методом, який приймає транзакцію як параметр, або ви можете спробувати отримати доступ до віддаленого компонента, що обслуговується. Це серіалізує об'єкт транзакції і призводить до ескалації, як коли транзація серіалізується через домен програми. Він розповсюджується, і локальний менеджер транзакцій вже не є адекватним.
Ми не відчуваємо №3. №2 цього не відбувається, оскільки одночасно існує лише одне з'єднання, а також до одного "довговічного ресурсу". Чи є спосіб, що №1 міг би відбуватися? Якась конфігурація SQL2005 / 8, через яку вона не підтримує однофазні сповіщення?
Оновлення 2:
Особисто повторно досліджено всі версії SQL Server - "Dev 3" насправді має SQL2008, а "Dev 4" насправді SQL2005. Це навчить мене ніколи більше не довіряти своїм колегам. ;) Через цю зміну даних я майже впевнений, що ми знайшли нашу проблему. Наші розробники SQL2008 не мали проблеми, тому що в SQL2008 включено велику кількість дивовижних, яких немає в SQL2005.
Це також говорить мені, що, оскільки ми будемо підтримувати SQL2005, ми не можемо використовувати TransactionScope, як ми були, і якщо ми хочемо використовувати TransactionScope, нам потрібно буде передавати один об'єкт SqlConnection навколо ... що здається проблематичним в ситуаціях, коли SqlConnection не може бути легко пройдений навколо ... він просто пахне глобальним екземпляром SqlConnection. П'ю!
Оновлення 3
Просто для уточнення тут у питанні:
SQL2008:
- Дозволяє безліч з'єднань в межах одного TransactionScope (як показано у наведеному вище прикладі коду.)
- Застереження №1: Якщо ці кілька SqlConnections вкладені, тобто два або більше SqlConnections відкриваються одночасно, TransactionScope негайно перейде до DTC.
- Застереження №2: Якщо додатковий SqlConnection відкриється для іншого "довговічного ресурсу" (тобто іншого сервера SQL,), він негайно перейде до DTC
SQL2005:
- Не дозволяє декілька з'єднань протягом одного TransactionScope, періоду. Він посилиться, коли / якщо буде відкрито друге 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 відкриє та закриє з'єднання, фактично закінчивши транзакцію для вас, тому що тепер ви не можете його знову відкрити.
Отже, для успішного використання TransactionScope з SQL2005 вам потрібно мати якийсь об'єкт глобального з’єднання, який залишається відкритим з точки першого TransactionScope, поки він більше не потрібен. Крім кодового запаху об'єкта глобального з'єднання, відкриття з'єднання спочатку та закриття його останнім суперечить логіці відкриття з'єднання якомога пізніше та закриття його якнайшвидшого.