2011-03-15 15 views
5

¿Hay alguna manera de agregar una restricción de verificación en una transacción y en caso de error al volver a un punto de rescate anterior (en lugar de revertir toda la transacción) ?Revertir transacción a punto de salvar en falla ALTER TABLE ... ADD CONSTRAINT

En mi caso, cuando falla un comando ALTER TABLE ... ADD CONSTRAINT, la transacción no se puede retrotraer al punto de rescate (el intento de hacerlo arroja una InvalidOperationException).

general para demostrar el punto crucial:

SqlTransaction transaction = connection.BeginTransaction(); 

// ... execute SQL commands on the transaction ... 

// Create savepoint 
transaction.Save("mySavepoint"); 

try 
{ 
    // This will fail... 
    SqlCommand boom = new SqlCommand(
     "ALTER TABLE table WITH CHECK ADD CONSTRAINT ...", 
     connection, 
     transaction); 

    boom.ExecuteNonQuery(); 
} 
catch 
{ 
    // ...and should be rolled back to the savepoint, but can't. 
    try 
    { 
     transaction.Rollback("mySavepoint"); 
    } 
    catch (InvalidOperationException) 
    { 
     // Instead, an InvalidOperationException is thrown. 
     // The transaction is unusable and can only be rolled back entirely. 
     transaction.Rollback(); 
    } 
} 

Y aquí está lista para ejecutar código de demostración para probar (se necesita una datase llamada "test"):

public class Demo 
{ 
    private const string _connectionString = "Data Source=(local);Integrated security=true;Initial Catalog=test;"; 
    private const string _savepoint = "save"; 
    private static readonly string _tableName = DateTime.Now.ToString("hhmmss"); 
    private static readonly string _constraintName = "CK" + DateTime.Now.ToString("hhmmss"); 

    private static readonly string _createTable = "CREATE TABLE [dbo].[" + _tableName + "] ([one] [int] NULL,[two] [int] NULL) ON [PRIMARY]"; 
    private static readonly string _insert1 = "INSERT INTO [" + _tableName + "] VALUES (1,1)"; 
    private static readonly string _addConstraint = "ALTER TABLE [dbo].[" + _tableName + "] WITH CHECK ADD CONSTRAINT [" + _constraintName + "] CHECK (([one]>(1)))"; 
    private static readonly string _insert2 = "INSERT INTO [" + _tableName + "] VALUES (2,2)"; 


    public static void Main(string[] args) 
    { 
     // Example code! Please ignore missing using statements. 

     SqlConnection connection = new SqlConnection(_connectionString); 
     connection.Open(); 

     SqlTransaction transaction = connection.BeginTransaction(); 

     SqlCommand createTable = new SqlCommand(_createTable, connection, transaction); 
     createTable.ExecuteNonQuery(); 

     // Create savepoint 
     transaction.Save(_savepoint); 

     SqlCommand insert1 = new SqlCommand(_insert1, connection, transaction); 
     insert1.ExecuteNonQuery(); 

     try 
     { 
      // This will fail... 
      SqlCommand boom = new SqlCommand(_addConstraint, connection, transaction); 
      boom.ExecuteNonQuery(); 
     } 
     catch 
     { 
      // ...and should be rolled back to the savepoint, but can't 
      transaction.Rollback(_savepoint); 
     } 

     SqlCommand insert2 = new SqlCommand(_insert2, connection, transaction); 
     insert2.ExecuteNonQuery(); 

     transaction.Commit(); 
     connection.Close(); 
    } 
} 
+0

Cuando intento puramente en TSQL me aparece el error "La transacción actual no se puede comprometer y no se puede revertir a un punto de rescate. Revertir la transacción completa". - Solo leyendo sobre transacciones condenadas. –

+0

No encontré ninguna documentación que indique explícitamente qué errores conducen a que se anule la transacción (o que no se pueda modificar), pero este error obviamente parece ser uno de ellos. –

+0

De hecho, la falta de documentación específica sobre ese tema es casi tan molesto. – nodots

Respuesta

0

I don' Creo que puede entremezclar el uso del punto de guardado en scripts y en C#. Realizo el siguiente SQL:

BEGIN TRANSACTION 

INSERT INTO Foos (Fooname) 
VALUES ('Bar1') 

SAVE TRANSACTION MySavePoint; 

INSERT INTO Foos (FooName) 
VALUES ('Bar2') 

ROLLBACK TRANSACTION MySavePoint 

COMMIT TRANSACTION 

Esto funcionará en SQL, y trabajará con el siguiente código:

using (SqlConnection conn = new SqlConnection("connectionString")) 
{ 
    conn.Open(); 

    using (SqlTransaction trans = conn.BeginTransaction()) 
    using (SqlCommand comm = new SqlCommand("The Above SQL", conn, trans)) 
    { 
     comm.ExecuteNonQuery(); 
     trans.Commit(); 
    } 
} 

Si intenta trans.Rollback("MySavePoint"); se producirá un error porque el objeto trans no está en control del punto de guardado - no lo sabe.

Si se divide el SQL a cabo en las dos inserciones independientes y utiliza el siguiente código:

using (SqlConnection conn = new SqlConnection("connectionString")) 
     { 
      conn.Open(); 

      using (SqlTransaction trans = conn.BeginTransaction()) 
      using (SqlCommand comm1 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar1')", conn, trans)) 
      using (SqlCommand comm2 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar2')", conn, trans)) 
      { 
       comm1.ExecuteNonQuery(); 
       trans.Save("MySavePoint"); 
       comm2.ExecuteNonQuery(); 
       trans.Rollback("MySavePoint"); 
       trans.Commit(); 
      } 
     } 

Se trabajará como se espera.

Solo una nota, deseche siempre los objetos que implementan IDisposable - preferiblemente en una declaración using.

Más información:

http://www.davidhayden.com/blog/dave/archive/2005/10/15/2517.aspx

Actualización: después faffing con esto durante un tiempo utilizando su código de ejemplo, parece que debido al error procedente de SQL, la transacción se está desplegando hacia atrás y se vuelve inutilizable Como se ha indicado en otra respuesta, parece que, junto con SQL, la transacción se revierte con fuerza independientemente de los puntos de rescate debido a ciertos errores. El único recurso para esto es reordenar los comandos ejecutados contra la base de datos y no confiar en los puntos de rescate, o al menos no confiar en que esa acción esté en un punto de rescate.

+0

Disculpe, no veo por qué la idea del punto de seguridad rompería ACID. En realidad ES un mecanismo de transacción, vea http://msdn.microsoft.com/en-us/library/ms188378.aspx – nodots

+0

@nodots oh, ya veo, venía de la perspectiva de un error desconocido que causaba la reversión, no una retroceso elegido condicionalmente debido a una acción no crítica que falla o no se necesita. Todavía me defiendo la idea de que las fallas que ocurren fuera de las condiciones previamente diseñadas deberían fallar en toda la transacción. Veo esos puntos de ahorro como una oportunidad para ejecutar acciones que no afectan la transacción. –

+0

@Adam: Aclaré la publicación original. No hay mezcla entre ellos. El problema es que el mecanismo de punto de rescate __básicamente__ funciona, pero no siempre (una instrucción ALTER TABLE ... ADD CONSTRAINT no pasa la transacción inutilizable). Además, no pude encontrar ninguna documentación sobre qué tipos de errores se pueden revertir a un punto de rescate y cuáles no. – nodots

1

Obtengo el mismo comportamiento cuando probé en TSQL.

BEGIN TRAN 

CREATE TABLE foo (col int) 

INSERT INTO foo values (1) 

SAVE TRANSACTION ProcedureSave; 

BEGIN TRY 
ALTER TABLE foo WITH CHECK ADD CONSTRAINT ck CHECK (col= 2) 
END TRY 
BEGIN CATCH 
    SELECT XACT_STATE() AS XACT_STATE 
    /*Returns -1, transaction is uncommittable. Next line will fail*/ 

    ROLLBACK TRANSACTION ProcedureSave 
    /*Msg 3931, Level 16, State 1: The current transaction cannot be committed and 
    cannot be rolled back to a savepoint. Roll back the entire transaction.*/ 
END CATCH 

GO 

SELECT @@TRANCOUNT AS [@@TRANCOUNT] /*Zero the transaction was rolled back*/ 

No he encontrado ninguna información en la documentación que los Estados que los errores podrían conducir a la transacción quedar condenados de esta manera. Creo que no existe tal documentación en this connect item comment.

La respuesta es que el manejo de errores es caso por caso. Depende no solo de la servidoridad , sino también del tipo de error y context. Desafortunadamente, no es ninguna lista publicada de manejo de errores comportamiento para diferentes errores.En en general, solo los errores servere deben matar la conexión y extremadamente el servidor de apagado . Pero cuando se trata de abortar declaración vs transacción abortar, es difícil resumir las reglas - es decir, es caso por caso.

+0

Noté que cuando ejecutaba el código de muestra, el OP siempre que la transacción perdería su conexión, pero la conexión permanecería abierta, un comportamiento muy extraño. –

+0

Gracias por desenterrar el elemento MS Connect, por lo que parece que puedo dejar de buscar documentación exhaustiva. :( – nodots

Cuestiones relacionadas