2010-04-23 31 views
15

En mi código C# estoy usando TransactionScope porque me dijeron que no confiara en que mis programadores sql siempre usarán transacciones y somos responsables y bla bla.TransactionScope and Transactions

Dicho esto

Parece objeto TransactionScope Retrotrae antes de la SqlTransaction? ¿Es eso posible y, en caso afirmativo, cuál es la metodología correcta para ajustar un TransactionScope en una transacción?

Aquí está la prueba de 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 

Y si me quedo esta me sale sólo la división por 0 y -1 en

llamada desde el código C# me sale un mensaje de error adicional

Se ha encontrado un error de división por cero.
El recuento de transacciones después de EXECUTE indica que falta una confirmación COMMIT o ROLLBACK TRANSACTION. Recuento anterior = 1, recuento actual = 0.

Si doy la transacción SQL un nombre entonces

No puede revertir SqlTransaction. No se encontró ninguna transacción o punto de rescate de ese nombre. El recuento de transacciones después de EXECUTE indica que falta una instrucción COMMIT o ROLLBACK TRANSACTION. Recuento anterior = 1, recuento actual = 2.

algunas veces parece que el recuento sube, hasta que la aplicación sale completamente

El C# es simplemente

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

      scope.Commit() 
     } 

EDIT:

El código sql tiene que funcionar para 2000 y 2005

Respuesta

21

Hubo una actualización masiva para manejar el error dentro de SQL Server 2005. Estos artículos son bastante extensa: Error Handling in SQL 2005 and Later by Erland Sommarskog y Error Handling in SQL 2000 – a Background by Erland Sommarskog

La mejor manera es algo como esto:

Crear el procedimiento almacenado como:

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 

Sin embargo, eso es solo para SQL Server 2005 en adelante.Sin utilizar los bloques TRY-CATCH en SQL Server 2005, es muy difícil eliminar todos los mensajes que SQL Server envía de vuelta. El extra messages que se refieren a son causados ​​por la naturaleza de cómo se manejan usando reversiones @@ trancount:

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

@@ trancount es una variable global que refleja el nivel de transacciones anidadas . Cada BEGIN TRANSACTION aumenta @@ trancount por 1, y cada COMMIT TRANSACTION disminuye @@ trancount por 1. Nada es en realidad cometido hasta @@ trancount llega a 0. ROLLBACK TRANSACTION deshace todo a la más exterior COMENZAR TRANSACCIÓN (a menos que haya usado el TRANSACCIÓN DE AHORRO bastante exótica), y fuerza @@ trancount a 0, respetando el valor anterior.

Al salir de un procedimiento almacenado, si @@ trancount no tiene el mismo valor como lo había hecho cuando el procedimiento de ejecución comenzado, SQL Server genera el error 266. no se produce este error, sin embargo, si el procedimiento se llama desde un disparador, directamente o indirectamente. Tampoco se planteó si está ejecutando con set implícitos TRANSACCIONES EN

Si no desea conseguir la advertencia acerca de la transacción cuenta que no se ajuste, es necesario tener una sola transacción abierta en un momento dado . Esto se hace mediante la creación de la totalidad de su procedimiento de la siguiente manera:

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 

De esta manera, sólo se emite los comandos de la transacción, si no está ya en una transacción. Si codifica todos sus procedimientos de esta manera, solo el procedimiento o el código C# que emite BEGIN TRANSACTION emitirá COMMIT/ROLLBACK y los recuentos de transacciones siempre coincidirán (no obtendrá un error).

en C# desde 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; 
} 

Es sólo una idea, pero es posible que pueda utilizar la captura TransactionAbortedException para obtener el error real y hacer caso omiso de la advertencia desajuste número de transacciones.

+0

@KM, tiene 'IF @@ trancount <0' al principio del proceso de muestra. ¿Puede @@transcount alguna vez ser negativo? No debería ser esto: 'IF @@ trancount = 0' ?? –

+0

@Charles Bretana, tienes razón, es un tipo-o. Lo arreglaré ... –

+0

@KM, Thx! funcionó en mi versión de prueba con el cambio, pero esto ha estado aquí desde abril/mayo ... Así que mi suposición natural es que me falta algo ... No estaba del todo seguro de una manera u otra ... Feliz ¡Vacaciones! –

1

Debe utilizar 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 

Y esta pregunta debe responder a su pregunta sobre TransactionScope y Rollbacks How does TransactionScope roll back transactions?

+0

me olvidó mencionar que esto tiene que trabajar para SQL 2000, una nd 2005, pero incluso para el 2005, cuál es la diferencia entre la instrucción if y el bloque try catch. El procedimiento de almacenamiento funciona de manera independiente, es decir, cuando se ejecuta en la ventana de consulta. – Mike

+0

el problema es que el error Dividir por Cero está bloqueando su SP. El try-catch está permitiendo que su código falle sin abandonar el SP. En la instrucción IF tan pronto como ocurre el error, se cierra el SP. – Glennular

+0

No estoy convencido de eso porque si ejecuto el sql anterior obtengo el resultado de -1 que está en la declaración de devolución de la condición if. – Mike

-1

Sé que esto es una sugerencia muy mundano, pero no habría una buena solución para ser evitar la división por cero en el primer lugar ? Prácticamente todas las operaciones DML (insertar, seleccionar, actualizar) pueden reescribirse para evitar dividir por ceros mediante el uso de sentencias CASE.

+1

Correcto. Pero la división por cero es un ejemplo, es por eso que elegí codificarlo. El problema es la forma correcta de capturar errores y usar el objeto de alcance de la transacción. – Mike

12

No utilice transacciones en tanto su código C# y los procedimientos almacenados. Uno es suficiente. Que casi siempre debería ser su código C#, solo que sabe qué conjunto de actualizaciones de la dbase deberían rechazarse o comprometerse en su totalidad.

+2

No estoy seguro de estar de acuerdo con eso. Podría ser el caso de que esté escribiendo una api de procedimiento almacenado destinada a ser consumida por muchas audiencias. Como autor de la API, sabría qué procedimientos almacenados deben tramitarse mejor que los clientes. (Podría ser algo tan mundano como un raiserror cuando @@ trancount es 0.) – Paul

2

Si tiene que admitir SQL Server 2000, use TransactionScope para hacer su vida más fácil. Sin embargo, ver en la parte inferior de por qué tiene limitaciones.

Tratamiento de errores SQL antes de que TRY/CATCH sea errático. El artículo de Erland publicado por KM explica los errores de declaración/alcance/aborto por lotes que lo hacen así. Básicamente, el código puede dejar de ejecutarse y queda bloqueado en las filas, etc.

Esto es lo que sucede arriba, por lo que su reversión no se ejecuta, por lo que obtiene el error 226 sobre recuentos de transacciones.

Si solo admite SQL Server 2005+, utilice TRY/CATCH que captura todos los errores y también usa SET XACT_ABORT ON. TRY/CATCH hace que SQL Server sea mucho más flexible y atrapa todos los errores de tiempo de ejecución. SET XACT_ABORT ON también suprime el error 226 porque emite la reversión automáticamente y asegura que se liberen todos los bloqueos.

Por cierto:

SELECT 1/0 es un excelente ejemplo de por qué debería utilizar el control de errores de SQL.

Uso del adaptador de datos para llenar un

  • un Datatable de un procedimiento almacenado con SELECT 1/0 -> ningún error atrapado
  • un conjunto de datos a partir de un procedimiento almacenado con SELECT 1/0 - atrapada> error

TRY SQL/CATCH se ocupará de esto ...

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); 
} 
Cuestiones relacionadas