2011-06-21 9 views
17

En el siguiente código en su caso excepción se produce durante la ejecución de las sentencias SQL que deberíamos esperar una reversión implícito en la transacción que la transacción no se ha cometido, se sale del ámbito y que se dispone:¿Es una mejor práctica llamar explícitamente la reversión de transacción o permitir que una excepción active una reversión implícita?

using (DbTransaction tran = conn.BeginTransaction()) 
{ 
    // 
    // Execute SQL statements here... 
    // 
    tran.Commit(); 
} 

¿Es lo anterior una práctica aceptable, o si uno detectar la excepción y explícitamente hacer una llamada a tran.Rollback() como se muestra a continuación:

using (DbTransaction tran = conn.BeginTransaction()) 
{ 
    try 
    { 
     // 
     // Execute SQL statements here... 
     // 
     tran.Commit(); 
    } 
    catch 
    { 
     tran.Rollback(); 
     throw; 
    } 
} 
+1

¿cuál transmite la mejor intención? –

Respuesta

19

Previamente. Si busca ejemplos de MSND en temas similares, como TransactionScope, todos están a favor de la reversión implícita. Hay varias razones para eso, pero le daré una muy simple: para cuando atrape la excepción, la transacción puede tener ya retrotraída. Muchos errores retrotraen la transacción pendiente y luego devuelven el control al cliente, donde ADO.Net genera la CLR SqlException después de la transacción ya se retrotrajo en el servidor (1205 DEADLOCK es el ejemplo típico de dicho error) , por lo que la llamada explícita Rollback() es, en el mejor de los casos, no operativa y, como mucho, un error. El proveedor del DbTransaction (por ejemplo, SqlTransaction) debe saber cómo manejar este caso, por ej. porque hay un chat explícito entre el servidor y el cliente notificando el hecho de que la transacción ya se ha revertido, y el método Dispose() hace lo correcto.

Una segunda razón es que las transacciones se pueden anidar, pero la semántica de ROLLBACK son que una reversión revierte todos transacciones, por lo que sólo tiene que llamar una vez (a diferencia de Commit() que compromete sólo los más transacciones interno y tiene ser llamado emparejado para cada comienzo). De nuevo, Dispose() hace lo correcto.

actualización

La muestra de MSDN para SqlConnection.BeginTransaction() favorece a la segunda forma explícita y hace un Rollback() en el bloque catch. Sospecho que el escritor técnico simplemente tenía la intención de mostrar en una sola muestra Rollback() y Commit(), observe cómo necesitaba agregar un segundo bloque try/catch alrededor del Rollback para eludir exactamente algunos de los problemas que mencioné originalmente.

+1

En realidad, el problema es que los proveedores de datos podrían no implementarlo de esa manera: "Eliminar debería deshacer la transacción. Sin embargo, el comportamiento de Dispose es específico del proveedor y no debería reemplazar la devolución de llamadas". a través de [MSDN] (http://msdn.microsoft.com/en-us/library/bf2cw321.aspx) –

+0

Dicho esto, prefiero el enfoque implícito. –

+0

@AndreLuus: Buena captura. Sin embargo, tenga en cuenta que el MSDN que ha vinculado tiene este texto solo para la versión 4.5, es decir. en el momento en que escribí la respuesta, esto aún no estaba en MSDN. –

1

Tiendo a estar de acuerdo con la reversión "implícita" en función de las vías de excepción. Pero, obviamente, eso depende de dónde se encuentre en la pila y de lo que intenta hacer (es decir, si la clase DBTranscation capta la excepción y realiza la limpieza, o ¿no está "comprometiendo" pasivamente?).

Aquí hay un caso en el que la manipulación implícita tiene sentido (tal vez):

static T WithTranaction<T>(this SqlConnection con, Func<T> do) { 
    using (var txn = con.BeginTransaction()) { 
     return do(); 
    } 
} 

Pero, si la API es diferente, el manejo de cometer podría ser también (de acuerdo, esto:

static T WithTranaction<T>(this SqlConnection con, Func<T> do, 
    Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null) 
{ 
    using (var txn = con.BeginTransaction()) { 
     try { 
      T t = do(); 
      success(txn); // does it matter if the callback commits? 
      return t; 
     } catch (Exception e) { 
      failure(txn); // does it matter if the callback rolls-back or commits? 
      // throw new Exception("Doh!", e); // kills the transaction for certain 
      // return default(T); // if not throwing, we need to do something (bogus) 
     } 
    } 
} 

No puedo pensar en demasiados casos en los que el repliegue explícito es el enfoque correcto, excepto cuando se aplica una estricta política de control de cambios. Pero, de nuevo, soy un poco lento en la adopción.

3

Puede ir de cualquier manera, siendo la primera más concisa, esta última siendo más reveladora.

Una advertencia con el primer enfoque sería que llamar a RollBack en la eliminación de la transacción depende de la implementación específica del controlador. Con suerte, casi todos los conectores .NET lo hacen. SqlTransaction hace:

private void Dispose(bool disposing) 
{ 
    Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID); 
    if (disposing && (this._innerConnection != null)) 
    { 
     this._disposing = true; 
     this.Rollback(); 
    } 
} 

de MySQL:

protected override void Dispose(bool disposing) 
{ 
    if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open) 
    Rollback(); 
    base.Dispose(disposing); 
} 

Un problema con la segunda aproximación es que no es seguro llamar RollBack sin otra try-catch. This is explicitly stated in the documentation.

En resumen, ¿qué es mejor? Depende del controlador, pero por lo general es mejor ir por la primera, por las razones mencionadas por Remus.

Consulte también What happens to an uncommitted transaction when the connection is closed? para saber cómo se compromete la eliminación de la conexión y se compromete.

Cuestiones relacionadas