W naszym projekcie używamy TransactionScope's, aby zapewnić, że nasza warstwa dostępu do danych wykonuje swoje działania w transakcji. Naszym celem jest nie wymagać, aby usługa MSDTC była włączona na naszych urządzeniach użytkowników końcowych.
Problem w tym, że na połowie maszyn naszych programistów możemy pracować z wyłączonym MSDTC. Druga połowa musi mieć to włączone lub otrzymają komunikat o błędzie „MSDTC na [SERWER] jest niedostępny” .
Naprawdę zmusza mnie do drapania się po głowie i poważnego zastanawiania się nad wycofaniem się do domowego rozwiązania TransactionScope, opartego na obiektach transakcyjnych ADO.NET. To pozornie szalony - ten sam kod, który działa (i nie eskalować) na pół naszego dewelopera robi eskalacji drugiej dewelopera.
Miałem nadzieję na lepszą odpowiedź dla Trace, dlaczego transakcja została eskalowana do DTC, ale niestety tak nie jest.
Oto przykładowy fragment kodu, który spowoduje problemy, na komputerach, które próbują eskalować, próbuje eskalować przy drugim połączeniu. Open () (i tak, w tym momencie nie jest otwarte żadne inne połączenie).
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();
}
Naprawdę wkopaliśmy się i próbowaliśmy to rozgryźć. Oto kilka informacji na temat maszyn, na których działa:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Programiści, na których nie działa:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- My Home PC: Windows Vista Home Premium, x86, SQL2005
Powinienem dodać, że wszystkie maszyny, starając się znaleźć problem, zostały w pełni załatane wszystkim, co jest dostępne z Microsoft Update.
Aktualizacja 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ opisuje podobny problem ... w 2006 roku!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - przeczytaj przykładowy kod, wyraźnie pokazuje połączenie zagnieżdżone z drugim (do drugiego serwera SQL, faktycznie), który przejdzie do DTC. Nie robimy tego w naszym kodzie - nie używamy różnych serwerów SQL, ani różnych ciągów połączeń, ani nie zagnieżdżamy otwierania połączeń wtórnych - nie powinno dojść do eskalacji do DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (od 2005 r.) mówi o tym, jak eskalacja do DTC zawsze będzie miała miejsce podczas łączenia z SQL2000. Używamy SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN na temat eskalacji transakcji.
Ta strona eskalacji transakcji MSDN stwierdza, że następujące warunki spowodują eskalację transakcji do DTC:
- W transakcji zarejestrowany jest co najmniej jeden trwały zasób, który nie obsługuje powiadomień jednofazowych.
- Transakcja obejmuje co najmniej dwa trwałe zasoby, które obsługują powiadomienia jednofazowe. Na przykład rejestracja pojedynczego połączenia nie powoduje promowania transakcji. Jednak za każdym razem, gdy otworzysz drugie połączenie z bazą danych, które powoduje zarejestrowanie bazy danych, infrastruktura System.Transactions wykrywa, że jest to drugi trwały zasób w transakcji, i przekształca go w transakcję MSDTC.
- Wywoływane jest żądanie „marshal” transakcji do innej domeny aplikacji lub innego procesu. Na przykład serializacja obiektu transakcji przez granicę domeny aplikacji. Obiekt transakcji jest zestawiany według wartości, co oznacza, że każda próba przekazania go przez granicę domeny aplikacji (nawet w tym samym procesie) powoduje serializację obiektu transakcji. Możesz przekazać obiekty transakcji, wywołując metodę zdalną, która przyjmuje jako parametr Transakcję lub możesz spróbować uzyskać dostęp do zdalnego komponentu obsługiwanego przez transakcję. Powoduje to serializację obiektu transakcji i skutkuje eskalacją, tak jak w przypadku serializacji transakcji w domenie aplikacji. Jest on dystrybuowany, a lokalny menedżer transakcji nie jest już odpowiedni.
Nie doświadczamy # 3. # 2 się nie dzieje, ponieważ istnieje tylko jedno połączenie na raz, a także z jednym „trwałym zasobem”. Czy jest jakikolwiek sposób, żeby mogło się zdarzyć # 1? Jakaś konfiguracja SQL2005 / 8, która powoduje, że nie obsługuje powiadomień jednofazowych?
Aktualizacja 2:
Osobiście ponownie zbadaliśmy wszystkie wersje SQL Server - „Dev 3” faktycznie ma SQL2008, a „Dev 4” to właściwie SQL2005. To nauczy mnie, że nigdy więcej nie zaufam moim współpracownikom. ;) Z powodu tej zmiany danych jestem pewien, że znaleźliśmy nasz problem. Nasi programiści SQL2008 nie mieli problemu, ponieważ SQL2008 zawiera wiele niesamowitych elementów, których nie ma SQL2005.
Mówi mi również, że ponieważ będziemy obsługiwać SQL2005, że nie możemy używać TransactionScope tak jak my, a jeśli chcemy użyć TransactionScope, będziemy musieli przekazać pojedynczy obiekt SqlConnection ... co wydaje się problematyczne w sytuacjach, w których SqlConnection nie można łatwo ominąć ... po prostu pachnie globalną instancją SqlConnection. Ławka w kościele!
Aktualizacja 3
Aby wyjaśnić tutaj w pytaniu:
SQL2008:
- Umożliwia wiele połączeń w ramach jednego TransactionScope (jak pokazano w powyższym przykładowym kodzie).
- Zastrzeżenie # 1: Jeśli zagnieżdżonych zostanie wiele SqlConnection, to znaczy, że dwa lub więcej SqlConnection zostanie otwartych w tym samym czasie, TransactionScope natychmiast eskaluje do DTC.
- Zastrzeżenie # 2: Jeśli dodatkowe SqlConnection zostanie otwarte dla innego „trwałego zasobu” (tj. Innego SQL Servera), natychmiast przejdzie do DTC
SQL2005:
- Nie zezwala na wiele połączeń w ramach jednego TransactionScope, kropka. Nastąpi eskalacja, gdy / jeśli zostanie otwarte drugie połączenie SqlConnection.
Aktualizacja 4
W interesie czyni to pytanie jeszcze bałagan użyteczne, a tylko przez wzgląd na większą przejrzystość, oto jak można dostać SQL2005 eskalować do DTC z pojedynczym SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Wydaje mi się to po prostu zepsute, ale chyba rozumiem, czy każde połączenie SqlConnection.Open()
jest pobierane z puli połączeń.
„Dlaczego jednak tak się dzieje?” Cóż, jeśli użyjesz SqlTableAdapter przed tym połączeniem przed jego otwarciem, SqlTableAdapter otworzy i zamknie połączenie, skutecznie kończąc transakcję za ciebie, ponieważ teraz nie możesz go ponownie otworzyć.
Zasadniczo więc, aby z powodzeniem korzystać z TransactionScope z SQL2005, potrzebujesz jakiegoś globalnego obiektu połączenia, który pozostaje otwarty od momentu pierwszego TransactionScope jest tworzony, dopóki nie będzie już potrzebny. Oprócz zapachu kodu globalnego obiektu połączenia, otwieranie połączenia jako pierwsze i zamykanie na końcu jest sprzeczne z logiką otwierania połączenia tak późno, jak to możliwe i zamykania go tak szybko, jak to możliwe.