В нашем проекте мы используем TransactionScope, чтобы гарантировать, что наш уровень доступа к данным выполняет свои действия в транзакции. Мы стремимся не требовать, чтобы служба MSDTC была включена на компьютерах наших конечных пользователей.
Проблема в том, что на половине машин наших разработчиков мы можем работать с отключенным MSDTC. Другая половина должна иметь его включенным, или они получают сообщение об ошибке «MSDTC на [SERVER] недоступен» .
Это действительно заставило меня поцарапать голову и всерьез подумать о том, чтобы вернуться к самодействующему TransactionScope-подобному решению, основанному на объектах транзакций ADO.NET. Это , казалось бы , с умом - тот же код , который работает (и не нагнетать) на половину нашего разработчика делает нагнетать на другом разработчик.
Я надеялся на лучший ответ Trace, почему транзакция переходит в 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
Я должен добавить, что все машины, чтобы выследить проблему, были полностью исправлены всем, что доступно из Центра обновления Microsoft.
Обновление 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 нагнетать ДТК с одного 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 до тех пор, пока он больше не понадобится. Помимо запаха кода глобального объекта соединения, открытие первого соединения и его закрытие последним противоречит логике открытия соединения как можно позже и закрытия его как можно скорее.