2010-04-23 12 views
15

Nel mio codice C# sto utilizzando TransactionScope perché mi è stato detto di non fare affidamento sul fatto che i miei programmatori sql utilizzeranno sempre le transazioni e siamo responsabili e yada yada.TransactionScope e Transazioni

Detto questo

Sembra oggetto TransactionScope rollback prima della SqlTransaction? È possibile e, in tal caso, qual è la metodologia corretta per il wrapping di TransactionScope in una transazione.

Ecco il 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 

E se corro questo ottengo solo la divisione per 0 e tornare -1

chiamata dal codice C# ricevo un messaggio di errore in più

Divide per errore zero rilevato.
Il conteggio transazioni dopo ESEGUI indica che manca una TOTAZIONE COMM. O TRANSAZIONE ROTONDA. Conteggio precedente = 1, conteggio corrente = 0.

Se io do un nome allora

non possa rotolare indietro SqlTransaction della transazione sql. Nessuna transazione o punto di salvataggio di quel nome è stata trovata. Il conteggio transazioni dopo EXECUTE indica che manca un'istruzione TRANSACTION COMMIT o ROLLBACK . Conteggio precedente = 1, conteggio corrente = 2.

alcune volte sembra il conteggio va in su, fino a quando l'applicazione esce completamente

Il C# è solo

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

      scope.Commit() 
     } 

EDIT:

Il codice sql deve funzionare per il 2000 e il 2005

risposta

21

C'è stato un massiccio aggiornamento per gestire l'errore in SQL Server 2005. Questi articoli sono abbastanza ampia: Error Handling in SQL 2005 and Later by Erland Sommarskog e Error Handling in SQL 2000 – a Background by Erland Sommarskog

Il modo migliore è qualcosa di simile:

Creare la stored procedure come:

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 

tuttavia è solo per SQL Server 2005 e versioni successive.Senza utilizzare i blocchi TRY-CATCH in SQL Server 2005, è molto difficile rimuovere tutti i messaggi restituiti da SQL Server. Il extra messages si riferiscono a sono causati dalla natura di come rollback vengono gestiti utilizzando @@ trancount:

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

@@ TRANCOUNT è una variabile globale che riflette il livello di nidificate transazioni. Ogni BEGIN TRANSAZIONE aumenti @@ trancount da 1, e ogni transazione di commit diminuisce @@ trancount di 1. Nulla è in realtà impegnata fino @@ trancount raggiunge lo 0. ROLLBACK TRANSACTION ripristina tutto al più esterno BEGIN TRANSACTION (a meno che non abbiate utilizzato la transazione SAVE TRANSACTION con esattezza e a 0, per quanto riguarda lo il valore precedente.

quando si esce da una stored procedure, se @@ trancount non ha lo stesso valore come aveva fatto quando la procedura S'inizia esecuzione, SQL Server genera errore 266. Questo errore non è sollevato, però, se la procedura è chiamata da un trigger, direttamente o indirettamente. Né è aumentata se si esegue con il set IMPLICITE OPERAZIONI ON

Se non si desidera ottenere l'avviso sulla transazione conteggio non corrispondono, è necessario disporre di una sola transazione aperta in qualsiasi momento . A tale scopo, la creazione di tutta la vostra procedura come questa:

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 

In questo modo, si emette comandi solo la transazione se non si è già in una transazione. Se si codificano tutte le procedure in questo modo, solo la procedura o il codice C# che emette la BEGIN TRANSACTION emetterà effettivamente COMMIT/ROLLBACK ei conteggi delle transazioni corrisponderanno sempre (non si otterrà un errore).

in C# da 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; 
} 

Solo un pensiero, ma si potrebbe essere in grado di utilizzare il TransactionAbortedException cattura per ottenere l'errore effettivo e ignorare l'avviso mancata corrispondenza conteggio delle transazioni.

+0

@KM, hai 'IF @@ trancount <0' all'inizio del processo di esempio. Il trancount @@ può mai essere negativo? Non dovrebbe essere "IF @@ trancount = 0' ?? –

+0

@Charles Bretana, hai ragione, è un tipo-o. Lo aggiusterò ... –

+0

@KM, Thx! ha funzionato nel mio test sproc con il cambiamento, ma questo è stato qui indisturbato da aprile/maggio ... Quindi il mio presupposto naturale è che mi manca qualcosa ... Non ero completamente sicuro in un modo o nell'altro ... Felice Vacanze ! –

1

Il tuo dovrebbe usare un try 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 

E questa domanda dovrebbe rispondere alla tua domanda su TransactionScope e rollback How does TransactionScope roll back transactions?

+0

ho dimenticato di dire che questo deve lavorare per SQL 2000 un Nel 2005, ma anche per il 2005, qual è la differenza tra la dichiarazione if e il blocco catch try. La procedura di memorizzazione funziona separatamente, ovvero quando viene eseguita nella finestra della query. – Mike

+0

il problema è che l'errore Divide by Zero sta bloccando il tuo SP. Il try-catch sta permettendo al tuo codice di fallire senza lasciare l'SP. Nella dichiarazione IF non appena si verifica l'errore, l'SP viene chiuso. – Glennular

+0

Non ne sono convinto, perché se eseguo la sql sopra riportata ottengo il risultato di -1, che è nella dichiarazione di ritorno della condtion if. – Mike

-1

So che questo è un suggerimento incredibilmente banali, ma non sarebbe una buona soluzione per essere quello di impedire la divisione per zero, in primo luogo ? Praticamente tutte le operazioni DML (inserimento, selezione, aggiornamento) possono essere riscritte per evitare la divisione per zeri tramite l'uso delle istruzioni CASE.

+1

corretto. Ma la divisione per zero è un esempio, è per questo che ho scelto di programmarlo con cura. Il problema è il modo corretto di catturare errori e utilizzare l'oggetto dell'ambito della transazione. – Mike

12

Non utilizzare le transazioni in sia il codice C# e le sprocs. Uno è abbastanza. Che quasi sempre dovrebbe essere il tuo codice C#, solo sa quale serie di aggiornamenti alla dbase deve essere rifiutata o impegnata in toto.

+2

Non sono sicuro di essere d'accordo. Potrebbe essere il caso in cui stai scrivendo un'API di stored procedure che è destinata a essere utilizzata da molti segmenti di pubblico. In quanto autore di api, sapresti quali procedure memorizzate devono essere gestite meglio dei clienti. (Potrebbe essere qualcosa di banale come un raiserror quando @@ trancount è 0.) – Paul

2

Se è necessario supportare SQL Server 2000, utilizzare TransactionScope per semplificare la vita. Tuttavia, vedi in fondo il motivo per cui ha limitazioni.

La gestione degli errori SQL prima di TRY/CATCH è errata. L'articolo di Erland pubblicato da KM spiega gli errori di istruzione/scope/batch che lo rendono così. Fondamentalmente, il codice potrebbe smettere di essere eseguito e ti rimangono blocchi su righe ecc.

Questo è ciò che accade sopra, quindi il tuo rollback non viene eseguito in modo da ottenere l'errore 226 sui conteggi delle transazioni.

Se si supporta solo SQL Server 2005+, quindi utilizzare TRY/CATCH che rileva tutti gli errori e utilizza anche SET XACT_ABORT ON. TRY/CATCH rende SQL Server molto più resiliente e intercetta tutti gli errori di runtime. SET XACT_ABORT ON sopprime inoltre l'errore 226 perché emette automaticamente il rollback e assicura che tutti i blocchi siano rilasciati.

BTW:

SELEZIONA 1/0 è un ottimo esempio del perché si dovrebbe usare la gestione degli errori SQL.

Utilizzare un DataAdapter per riempire

  • un DataTable da una stored procedure con SELECT 1/0 -> nessun errore intrappolati
  • un DataSet da una stored procedure con SELECT 1/0 -> errore intrappolato

SQL try/catch si occuperà di questo ...

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); 
} 
Problemi correlati