2010-05-27 15 views
63

Tengo un bloque de código que se ejecuta dentro de un TransactionScope y dentro de este bloque de código realizo varias llamadas al DB. Selecciona, actualiza, crea y elimina toda la gama. Cuando ejecuto mi eliminación, la ejecuto usando un método de extensión del SqlCommand que reenviará automáticamente la consulta si se bloquea, ya que esta consulta podría potencialmente llegar a un punto muerto.TransactionScope completado prematuramente

Creo que el problema ocurre cuando se golpea un interbloqueo y la función intenta volver a enviar la consulta. Este es el error que recibo:

La transacción asociada con la conexión actual se ha completado pero no se ha eliminado. La transacción debe eliminarse antes de que la conexión se pueda usar para ejecutar sentencias de SQL.

Este es el código simple que ejecuta la consulta (todo el código a continuación ejecuta dentro del uso de la TransactionScope):

using (sqlCommand.Connection = new SqlConnection(ConnectionStrings.App)) 
{ 
    sqlCommand.Connection.Open(); 
    sqlCommand.ExecuteNonQueryWithDeadlockHandling(); 
} 

Aquí es el método de extensión que somete de nuevo la consulta de un punto muerto:

public static class SqlCommandExtender 
{ 
    private const int DEADLOCK_ERROR = 1205; 
    private const int MAXIMUM_DEADLOCK_RETRIES = 5; 
    private const int SLEEP_INCREMENT = 100; 

    public static void ExecuteNonQueryWithDeadlockHandling(this SqlCommand sqlCommand) 
    { 
     int count = 0; 
     SqlException deadlockException = null; 

     do 
     { 
      if (count > 0) Thread.Sleep(count * SLEEP_INCREMENT); 
      deadlockException = ExecuteNonQuery(sqlCommand); 
      count++; 
     } 
     while (deadlockException != null && count < MAXIMUM_DEADLOCK_RETRIES); 

     if (deadlockException != null) throw deadlockException; 
    } 

    private static SqlException ExecuteNonQuery(SqlCommand sqlCommand) 
    { 
     try 
     { 
      sqlCommand.ExecuteNonQuery(); 
     } 
     catch (SqlException exception) 
     { 
      if (exception.Number == DEADLOCK_ERROR) return exception; 
      throw; 
     } 

     return null; 
    } 
} 

el error se produce en la línea:

sqlCommand.ExecuteNonQuery(); 

Respuesta

59

No olvide suprimir sus extractos seleccionados de su TransactionScope. En SQL Server 2005 y versiones posteriores, incluso cuando usa con (nolock), los bloqueos aún se crean en esas tablas que toca la selección. Mira esto, te muestra how to setup and use TransactionScope.

using(TransactionScope ts = new TransactionScope 
{ 
    // db calls here are in the transaction 
    using(TransactionScope tsSuppressed = new TransactionScope (TransactionScopeOption.Suppress)) 
    { 
    // all db calls here are now not in the transaction 
    } 
} 
+1

Gracias por la sugerencia sobre la supresión de transacciones en sentencias seleccionadas. Eso ayudó a resolver un problema de tiempo de espera que me estaba volviendo loco. –

+2

Fantástica respuesta. Eso me estaba volviendo loco en una colección de colección de selección/inserción de instrucciones sql. Agregar la opción Suprimir resuelve el problema automáticamente. – IoChaos

+1

¡Santa mierda, gracias! He estado luchando con esto todo el día. Una solución tan simple. – rossipedia

10

Si se produce una excepción dentro de un TransactionScope, se revierte. Esto significa que TransactionScope está hecho. Ahora debe llamar al dispose() y comenzar una nueva transacción. Honestamente, no estoy seguro si puedes reutilizar el viejo TransactionScope o no, nunca lo intenté, pero supongo que no.

+2

Incluso si se detecta la excepción, la transacción se revierte? – Chris

+0

Nunca he experimentado con eso en mi caso, excepción = error = detener y revertir. Sin embargo, parece ser por lo que describes. – Donnie

+1

Esto es falso, y no es lo que sucede en las excepciones. Tampoco tiene que llamar a Dispose() entonces. Cuando se genera TransactionScope en una instrucción de uso, la instrucción de uso desechará() TransactionScope on Exceptions. – thewhiteambit

20

Puedo reproducir el problema. Es un tiempo de espera de transacción.

using (new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0, 0, 1))) 
{ 
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    { 
     connection.Open(); 
     using (var sqlCommand = connection.CreateCommand()) 
     { 
      for (int i = 0; i < 10000; i++) 
      { 
       sqlCommand.CommandText = "select * from actor"; 
       using (var sqlDataReader = sqlCommand.ExecuteReader()) 
       { 
        while (sqlDataReader.Read()) 
        { 
        } 
       } 
      } 
     } 
    } 
} 

Lanza System.InvalidOperationException con este mensaje: "La transacción asociada a la conexión actual ha completado pero no se ha dispuesto que la transacción debe ser eliminado antes de la conexión puede ser utilizado para ejecutar sentencias SQL.".

Para resolver el problema, haga que la consulta se ejecute más rápido o aumente el tiempo de espera.

6

Mi problema era estúpido, si te sientas en un error de depuración durante el tiempo de espera lo obtendrás. palma de la cara

hombre, la programación hace sentir gruesa algunos días ...

39

me he dado cuenta que este mensaje puede ocurrir cuando una transacción se ejecuta durante un período más largo que el maxTimeout para System.Transactions. No importa que aumente TransactionOptions.Timeout, no puede exceder maxTimeout.

El valor predeterminado de maxTimeout se establece en 10 minutos y su valor puede solamente ser modificado en el machine.config

Añadir lo siguiente (en el nivel de configuración) a la machine.config para modificar el tiempo de espera:

<configuration> 
    <system.transactions> 
     <machineSettings maxTimeout="00:30:00" /> 
    </system.transactions> 
</configuration> 

El machine.config se puede encontrar en: %windir%\Microsoft.NET\Framework\[version]\config\machine.config

Usted puede leer más sobre esto en esta entrada del blog: http://thecodesaysitall.blogspot.se/2012/04/long-running-systemtransactions.html

+3

Tenga en cuenta que el archivo de configuración distingue entre mayúsculas y minúsculas, por lo que debe ser "machineSettings" y "maxTimeout". Lástima que no puede anular esto en su archivo app.config :( –

+1

También tenga en cuenta que * debe * poner esto en el * fin * de la sección de configuración, de lo contrario, obtendrá un error. –

6

Confirmado este error también puede deberse a un tiempo de espera de transacción. Solo para agregar a lo que Marcus + Rolf ha indicado, si no ha establecido explícitamente un tiempo de espera en el TransactionScope, el tiempo de espera TimeSpan asumirá un valor predeterminado. Este valor por defecto es el más pequeño de:

  1. Si ha redefinido el ámbito local app.config/web.config, por ejemplo,

    <system.transactions> 
    <defaultSettings timeout="00:05:00" /> 
    </system.transactions> 
    
  2. Pero esto es luego 'tapado' en la configuración de machine.config<machineSettings maxTimeout="00:10:00" />

+0

¿Se puede * override_ esto en * ** archivo app.config ***? – Kiquenet

1

Esta excepción también puede ser causada por desactivarMicrosoft Distributed Transaction Coordinator.

Si queremos activarlo, corremos "DCOMCnfg" y seleccione "Component Services" -> "My Computer" -> "Distributed Transaction Coordinator" -> "Local Service DTC" y elija "Propiedades ".

se debe comprobar "permitir que el cliente remoto", "Permitir entrantes", "Permitir salientes" y "No se requiere autenticación".

+0

¿Puedes *** activar/desactivar *** _programmatically_ usando 'BAT' o' ps1'? – Kiquenet

Cuestiones relacionadas