В нашем проекте мы используем TransactionScope для обеспечения того, чтобы наш уровень доступа к данным выполнял его действия в транзакции. Мы стремимся не требовать включения службы MSDTC на наших конечных машинах.
Проблема в том, что на половине наших машин-разработчиков мы можем работать с отключенным MSDTC. Другая половина должна быть включена, или они получают сообщение об ошибке "MSDTC в [SERVER] недоступно".
Это действительно заставило меня почесывать голову, и я серьезно подумываю о том, чтобы вернуться к основанному на транзакциях решению TransactionScope, основанному на объектах транзакций ADO.NET. Кажется безумным - тот же код, который работает (и не эскалируется), на половине нашего разработчика делает эскалацию на другом разработчике.
Я надеялся на лучший ответ Отслеживать, почему транзакция перерастает в DTC, но, к сожалению, этого не происходит.
Здесь примерный бит кода, который вызовет проблему, на машинах, которые пытаются эскалировать, пытается обойтись во втором соединении .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();
}
Мы действительно вырыли и попытались понять это. Вот некоторая информация о машинах, на которых она работает:
- 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
Я должен добавить, что все машины, чтобы выследить проблему, были полностью исправлены всем, что доступно в Центре обновления Майкрософт.
Обновление 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 - прочитайте этот пример кода, он наглядно демонстрирует вложенное второе соединение (на второй 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.Transactions обнаруживает, что она является вторым долговременным ресурсом в транзакции, и увеличивает ее до транзакции MSDTC.
- Вызывается запрос на "маршалирование" транзакции к другому домену приложения или к другому процессу. Например, сериализация объекта транзакции на границе домена приложения. Объект транзакции маршалируется по значению, что означает, что любая попытка передать его через границу домена приложения (даже в том же процессе) приводит к сериализации объекта транзакции. Вы можете передать объекты транзакций, совершив вызов удаленного метода, который принимает транзакцию в качестве параметра, или вы можете попытаться получить доступ к удаленному компоненту, обслуживаемому транзакциями. Это сериализует объект транзакции и приводит к эскалации, например, когда транзакция транслируется по всему домену приложения. Он распространяется, и локальный менеджер транзакций уже не является адекватным.
Мы не испытываем # 3. # 2 не происходит, потому что есть только одна связь за раз, а также один "долговечный ресурс". Есть ли способ, которым № 1 может случиться? Некоторая конфигурация SQL2005/8, которая заставляет его не поддерживать однофазные уведомления?
Обновление 2:
Повторно расследовали лично все версии SQL Server - "Dev 3" на самом деле имеет SQL2008, а "Dev 4" на самом деле SQL2005. Это научит меня больше никогда не доверять моим коллегам.;) Из-за этого изменения данных, я уверен, что мы нашли нашу проблему. Наши разработчики SQL2008 не сталкивались с этой проблемой, потому что SQL2008 содержит огромное количество удивительных включений, которые SQL2005 не имеет.
Он также говорит мне, что, поскольку мы будем поддерживать SQL2005, что мы не можем использовать TransactionScope, как мы были, и если мы хотим использовать TransactionScope, нам нужно будет передать один объект SqlConnection вокруг... который кажется проблематичным в ситуациях, когда SqlConnection не может быть легко передан... он просто пахнет экземпляром global-SqlConnection. Pew!
Обновление 3
Просто уточнить здесь в вопросе:
SQL2008:
- Позволяет несколько подключений в пределах одного TransactionScope (как показано в приведенном выше примере кода.)
- Предостережение # 1: Если эти множественные SqlConnections вложены, то есть одновременно открываются два или более SqlConnections, TransactionScope немедленно перейдет в DTC.
- Предостережение # 2: Если дополнительное SqlConnection открывается для другого "долговременного ресурса" (то есть: другой SQL Server), он немедленно перейдет в DTC
SQL2005:
- Не разрешает несколько подключений в течение одного периода TransactionScope. Он будет эскалироваться, когда/если будет открыто второе SqlConnection.
Обновление 4
В интересах сделать этот вопрос еще более приятным беспорядка, и просто для большей наглядности, вот как вы можете заставить SQL2005 переходить на DTC с помощью одного 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, созданный до тех пор, пока он больше не понадобится. Помимо запаха кода глобального объекта соединения, сначала открывая соединение и закрывая его, он находится в противоречии с логикой открытия соединения как можно позже и как можно скорее закрывает его.