2010-04-23 9 views
15

W moim kod C# używam TransactionScope, ponieważ powiedziano mi, aby nie polegać na tym, że moi programiści sql zawsze będą korzystać z transakcji, a my jesteśmy odpowiedzialni i Yada Yada.TransactionScope i Transakcje

Mimo, że

Wygląda jak przedmiot TransactionScope Wycofuje przed SqlTransaction? Czy jest to możliwe, a jeśli tak, to jaka jest właściwa metodologia zawijania TransactionScope w transakcji.

Oto test SQL

CREATE PROC ThrowError 
AS 

BEGIN TRANSACTION --SqlTransaction 
SELECT 1/0 

IF @@ERROR<> 0 
BEGIN 
    ROLLBACK TRANSACTION --SqlTransaction 
    RETURN -1 
END 
ELSE 
BEGIN 
    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 
END 

go 

DECLARE @RESULT INT 

EXEC @RESULT = ThrowError 

SELECT @RESULT 

A jeśli uruchomię to uzyskać tylko dzielenie przez 0 i zwracają -1

połączenia z kodu C# i uzyskać dodatkowy komunikat o błędzie

Napotkano błąd dzielenia przez zero.
Licznik transakcji po WYKONANIU wskazuje, że brak jest powiązania COMMIT lub ROLLBACK TRANSACTION. Poprzedni count = 1, prąd count = 0.

Jeśli dam Transakcja sql nazwę następnie

nie można wycofać SqlTransaction. Nie znaleziono transakcji ani punktu zapisu o tej nazwie. Liczba transakcji po WYKONANIU wskazuje, że brakuje instrukcji COMMIT lub ROLLBACK TRANSACTION. Poprzedni count = 1, prąd count = 2.

czasami wydaje się, że liczba idzie w górę, aż aplikacja całkowicie wychodzi

C# jest tylko

 using (TransactionScope scope = new TransactionScope()) 
     { 
      ... Execute Sql 

      scope.Commit() 
     } 

EDIT:

Kod SQL musi zadziałać w 2000 i 2005 roku:

Odpowiedz

21

Był masywny upgrade do obsługi błędu wewnątrz SQL Server 2005. Te artykuły są dość rozległe: Error Handling in SQL 2005 and Later by Erland Sommarskog i Error Handling in SQL 2000 – a Background by Erland Sommarskog

Najlepszym sposobem jest coś takiego:

Utwórz procedurę przechowywaną jak:

CREATE PROCEDURE YourProcedure 
AS 
BEGIN TRY 
    BEGIN TRANSACTION --SqlTransaction 
    DECLARE @ReturnValue int 
    SET @ReturnValue=NULL 

    IF (DAY(GETDATE())=1 --logical error 
    BEGIN 
     SET @ReturnValue=5 
     RAISERROR('Error, first day of the month!',16,1) --send control to the BEGIN CATCH block 
    END 

    SELECT 1/0 --actual hard error 

    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 

END TRY 
BEGIN CATCH 
    IF XACT_STATE()!=0 
    BEGIN 
     ROLLBACK TRANSACTION --only rollback if a transaction is in progress 
    END 

    --will echo back the complete original error message to the caller 
    --comment out if not needed 
    DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int 

    SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE() 
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine) 

    RETURN ISNULL(@ReturnValue,1) 

END CATCH 

GO 

jest to jednak tylko dla SQL Server 2005 i nowszych.Bez użycia bloków TRY-CATCH w SQL Server 2005, bardzo trudno jest usunąć wszystkie wiadomości odesłane przez program SQL Server. extra messages odnieść się do spowodowanych przez naturę jak cofanie są obsługiwane za pomocą @@ TRANCOUNT:

z http://www.sommarskog.se/error-handling-I.html#trancount

@@ trancount jest zmienna globalna który odzwierciedla poziom zagnieżdżonych transakcji. Każdy rozpocząć transakcji wzrosty @@ TRANCOUNT o 1, a każdy dokonać transakcji maleje @@ trancount o 1. Nic nie jest faktycznie popełnił aż @@ trancount osiągnie 0. cofnąć transakcji wycofuje wszystko do zewnętrznej BEGIN transakcji (chyba że użyłeś dość egzotycznego ZAPISZ TRANSAKCJĘ) i wymusza @@ trankount na 0, poznaj poprzedniej wartości.

Po wyjściu z procedury przechowywanej, jeśli @@ trancount nie mają taką samą wartość jak to było, gdy procedura rozpoczęta realizacja, SQL Server podnosi błąd 266. Ten błąd nie jest podniesiony, chociaż, jeśli procedura nosi nazwę z wyzwalacza, bezpośrednio lub pośrednio. Nie jest ona podniesiona jeśli używasz z zestawem niejawna TRANSAKCJI NA

Jeśli nie chcesz dostać ostrzeżenie o transakcji liczyć nie pasuje, trzeba mieć tylko jedną transakcję otwarty w jednym czasie . Można to zrobić, tworząc całą procedurę, taką jak ta:

CREATE PROC YourProcedure 
AS 
DECLARE @SelfTransaction char(1) 
SET @SelfTransaction='N' 

IF @@trancount=0 
BEGIN 
    SET @SelfTransaction='Y' 
    BEGIN TRANSACTION --SqlTransaction 
END 

SELECT 1/0 

IF @@ERROR<> 0 
BEGIN 
    IF @SelfTransaction='Y' 
    BEGIN 
     ROLLBACK TRANSACTION --SqlTransaction 
    END 
    RETURN -1 
END 
ELSE 
BEGIN 
    IF @SelfTransaction='Y' 
    BEGIN 
     COMMIT TRANSACTION --SqlTransaction 
    END 
    RETURN 0 
END 

GO 

W ten sposób wydaje się tylko polecenia transakcji, jeśli jeszcze nie jesteś w transakcji. Jeśli w ten sposób zakodujesz wszystkie swoje procedury, tylko procedura lub kod C#, który wyda BEGIN TRANSACTION, wyda polecenie COMMIT/ROLLBACK, a liczba transakcji zawsze będzie zgodna (nie otrzymasz błędu).

w C# od TransactionScope Class Documentation:

static public int CreateTransactionScope(
    string connectString1, string connectString2, 
    string commandText1, string commandText2) 
{ 
    // Initialize the return value to zero and create a StringWriter to display results. 
    int returnValue = 0; 
    System.IO.StringWriter writer = new System.IO.StringWriter(); 

    try 
    { 
     // Create the TransactionScope to execute the commands, guaranteeing 
     // that both commands can commit or roll back as a single unit of work. 
     using (TransactionScope scope = new TransactionScope()) 
     { 
      using (SqlConnection connection1 = new SqlConnection(connectString1)) 
      { 
       // Opening the connection automatically enlists it in the 
       // TransactionScope as a lightweight transaction. 
       connection1.Open(); 

       // Create the SqlCommand object and execute the first command. 
       SqlCommand command1 = new SqlCommand(commandText1, connection1); 
       returnValue = command1.ExecuteNonQuery(); 
       writer.WriteLine("Rows to be affected by command1: {0}", returnValue); 

       // If you get here, this means that command1 succeeded. By nesting 
       // the using block for connection2 inside that of connection1, you 
       // conserve server and network resources as connection2 is opened 
       // only when there is a chance that the transaction can commit. 
       using (SqlConnection connection2 = new SqlConnection(connectString2)) 
       { 
        // The transaction is escalated to a full distributed 
        // transaction when connection2 is opened. 
        connection2.Open(); 

        // Execute the second command in the second database. 
        returnValue = 0; 
        SqlCommand command2 = new SqlCommand(commandText2, connection2); 
        returnValue = command2.ExecuteNonQuery(); 
        writer.WriteLine("Rows to be affected by command2: {0}", returnValue); 
       } 
      } 

      // The Complete method commits the transaction. If an exception has been thrown, 
      // Complete is not called and the transaction is rolled back. 
      scope.Complete(); 
     } 
    } 
    catch (TransactionAbortedException ex) 
    { 
     writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message); 
    } 
    catch (ApplicationException ex) 
    { 
     writer.WriteLine("ApplicationException Message: {0}", ex.Message); 
    } 

    // Display messages. 
    Console.WriteLine(writer.ToString()); 

    return returnValue; 
} 

Tylko myśl, ale może być w stanie korzystać z TransactionAbortedException zaczep, aby uzyskać rzeczywisty błąd i ignoruje ostrzeżenia niezgodności licznika transakcji.

+0

@KM, Masz 'IF @@ trankount <0' wcześnie w przykładowym proc. Czy transkonto @@ może być zawsze ujemne? Nie powinno to być 'IF @@ trankount = 0' ?? –

+0

@Charles Bretana, masz rację, to jest typ-o. Naprawię to ... –

+0

@KM, Thx! to działało w moim teście sproc ze zmianą, ale to jest tutaj niezakłócone od kwietnia/maja ... Tak więc moim naturalnym założeniem jest to, że czegoś mi brakuje ... Nie byłem do końca pewien w ten czy inny sposób ... Happy Wakacje ! –

1

Twój powinien użyć funkcji catch catch

BEGIN TRANSACTION --SqlTransaction 
BEGIN TRY 
    SELECT 1/0 
    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 
END TRY 
BEGIN CATCH 
    ROLLBACK TRANSACTION --SqlTransaction 
    RETURN -1 
END CATCH 

I to pytanie należy odpowiedzieć na pytanie o TransactionScope i cofanie How does TransactionScope roll back transactions?

+0

Zapomniałem wspomnieć, że musi pracować dla sql 2000 r nd 2005, ale nawet w 2005 r. jaka jest różnica między instrukcją if a blokiem catch catch. Procedura sklepu działa samodzielnie, czyli działa w oknie zapytania. – Mike

+0

Problem polega na tym, że błąd Divide by Zero powoduje awarię twojego SP. Funkcja "catch-catch" pozwala na niepowodzenie kodu bez opuszczania SP. W poleceniu IF natychmiast po wystąpieniu błędu SP zostaje zakończony. – Glennular

+0

Nie jestem tego przekonany, ponieważ jeśli uruchomię powyższy sql, otrzymam wynik -1, który znajduje się w instrukcji return warunku if. – Mike

-1

wiem, że to jest bardzo przyziemne sugestia, ale nie byłoby to dobre rozwiązanie, aby być, aby zapobiec dzielenie przez zero w pierwszej kolejności ? Niemal wszystkie operacje DML (wstawianie, wybieranie, aktualizacja) mogą być przepisywane, aby uniknąć dzielenia przez zera za pomocą instrukcji CASE.

+1

Prawidłowo. Ale dzielenie przez zero jest przykładem, dlatego zdecydowałem się go zakodować. Problemem jest poprawny sposób przechwytywania błędów i korzystania z obiektu zakresu transakcji. – Mike

12

Nie używaj transakcji zarówno twój kod C# i sprocs. Jeden wystarczy. Który prawie zawsze powinien być twoim C# code, tylko on wie, który zestaw aktualizacji do bazy danych powinien zostać odrzucony lub zatwierdzony w całości.

+2

Nie jestem pewien, czy się z tym zgadzam. Może się tak zdarzyć, że piszesz api procedury przechowywanej, która ma być używana przez wielu odbiorców. Jako autor api wiedziałbyś, jakie procedury przechowywane wymagają lepszej transakcji niż klienci. (Może to być coś tak przyziemnego, jak przypadek, gdy @@ trancount wynosi 0.) – Paul

2

Jeśli musisz obsługiwać program SQL Server 2000, użyj TransactionScope, aby ułatwić sobie życie. Zobacz jednak na dole, dlaczego ma ograniczenia.

Obsługa błędów SQL przed TRY/CATCH jest błędna. Artykuł Erlanda zamieszczony przez KM wyjaśnia błędy, które powodują, że są to błędy instrukcji/zakresu/batchowania. Zasadniczo, kod może po prostu przestać wykonywać, a ty pozostajesz z blokadami w wierszach itp.

To właśnie dzieje się powyżej, więc wycofywanie nie działa, więc pojawia się błąd 226 dotyczący liczby transakcji.

Jeśli obsługujesz tylko SQL Server 2005+, użyj TRY/CATCH, który przechwytuje wszystkie błędy, a także użyj SET XACT_ABORT ON. TRY/CATCH sprawia, że ​​SQL Server jest znacznie bardziej odporny i przechwytuje wszystkie błędy środowiska wykonawczego. SET XACT_ABORT ON również pomija błąd 226, ponieważ powoduje automatyczne wycofywanie i zapewnia zwolnienie wszystkich blokad.

BTW:

SELECT 1/0 jest doskonałym przykładem tego, dlaczego należy użyć obsługę błędów SQL.

Użyj DataAdapter aby wypełnić

  • DataTable z przechowywanych proc z wybranymi 1/0 -> Brak błędów w pułapce
  • DataSet z przechowywanych proc z SELECT 1/0 -> błąd w pułapce

SQL TRY/CATCH będzie sobie z tym poradzić ...

0
public string ExecuteReader(string SqlText) 
{ 
    SqlCommand cmd; 
    string retrunValue = ""; 
    try 
    { 
     c.Open(); 
     cmd = new SqlCommand(); 
     cmd.CommandType = CommandType.Text;     
     cmd.Connection = c; 
     cmd.CommandText = SqlText; 
     retrunValue = Convert.ToString(cmd.ExecuteScalar()); 
     c.Close(); 
    } 
    catch (Exception SqlExc) 
    { 
     c.Close(); 
     throw SqlExc; 

    } 
    return (retrunValue); 
} 
Powiązane problemy