2010-05-27 8 views
63

Ho un blocco di codice eseguito all'interno di TransactionScope e all'interno di questo blocco di codice eseguo diverse chiamate al DB. Seleziona, aggiorna, crea ed elimina, l'intera gamma. Quando eseguo la mia eliminazione, la eseguo utilizzando un metodo di estensione di SqlCommand che ripubblicherà automaticamente la query se si blocca in quanto questa query potrebbe potenzialmente raggiungere un deadlock.TransactionScope precocemente completato

Credo che il problema si verifichi quando si verifica un deadlock e la funzione tenta di inviare nuovamente la query. Questo è l'errore che ricevo:

La transazione associata alla connessione corrente è stata completata ma non è stata eliminata. La transazione deve essere eliminata prima che la connessione possa essere utilizzata per eseguire istruzioni SQL.

Questo è il semplice codice che esegue la query (tutto il codice sotto esegue all'interno usando del TransactionScope):

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

Qui è il metodo di estensione che invia nuovamente la query punto morto:

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; 
    } 
} 

l'errore si verifica sulla linea:

sqlCommand.ExecuteNonQuery(); 

risposta

59

Non dimenticare di annullare le istruzioni di selezione dal TransactionScope. In SQL Server 2005 e versioni successive, anche quando si utilizza con (nolock), i blocchi vengono ancora creati su quelle tabelle selezionate. Controlla questo, ti mostra 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

Grazie per il suggerimento sulla soppressione delle transazioni sulle istruzioni select. Questo ha aiutato a risolvere un problema di timeout che mi stava facendo impazzire. –

+2

Risposta fantastica. Questo mi ha fatto impazzire su una raccolta di raccolta selezione/inserimento di istruzioni SQL. L'aggiunta dell'opzione Elimina automaticamente risolve il problema. – IoChaos

+1

Santo schifo grazie! Sono stato a lottare con questo tutto il giorno. Una soluzione così semplice. – rossipedia

10

Se si verifica un'eccezione all'interno di TransactionScope, viene eseguito il rollback. Ciò significa che lo TransactionScope è terminato. Ora devi chiamare dispose() e avviare una nuova transazione. Onestamente non sono sicuro che tu possa riutilizzare il vecchio TransactionScope o no, non ci ho mai provato, ma suppongo di no.

+2

Anche se viene rilevata un'eccezione, la transazione viene annullata? – Chris

+0

Non l'ho mai sperimentato come per me, exception = error = stop e rollback. Tuttavia, sembra così da ciò che stai descrivendo. – Donnie

+1

Questo è falso e non è ciò che accade sulle eccezioni. Inoltre, non è necessario chiamare Dispose(). Quando TransactionScope viene generato in un'istruzione using, l'istruzione using disporrà() TransactionScope su Exceptions. – thewhiteambit

20

Posso riprodurre il problema. È un timeout della transazione.

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()) 
        { 
        } 
       } 
      } 
     } 
    } 
} 

Lanci System.InvalidOperationException con questo messaggio: "La transazione associato alla connessione corrente ha completato ma non è stato smaltito La transazione deve essere smaltito prima la connessione può essere utilizzata per eseguire istruzioni SQL.".

Per risolvere il problema, eseguire la query più rapidamente o aumentare il timeout.

6

Il mio problema è stato uno stupido, se ci si siede su una pausa di debug attraverso il timeout si otterrà questo. Viso Palm

uomo, la programmazione fa sentire spessa qualche giorno ...

39

ho trovato che questo messaggio può verificarsi quando una transazione viene eseguito per un periodo più lungo del maxTimeout per System.Transactions. Non importa che TransactionOptions.Timeout sia aumentato, non può superare maxTimeout.

Il valore predefinito di maxTimeout è impostato su 10 minuti e il suo valore può solo essere modificato nella machine.config

Aggiungere il seguente (nel livello configurazione) al machine.config modificare il timeout:

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

Machine.config è disponibile all'indirizzo: %windir%\Microsoft.NET\Framework\[version]\config\machine.config

si può leggere di più su di esso in questo post del blog: 0.123.894,07365 milioni

+3

Ricordare che il file di configurazione fa distinzione tra maiuscole e minuscole, pertanto dovrebbe essere "machineSettings" e "maxTimeout". Peccato che non puoi sovrascriverlo nel tuo file app.config :( –

+1

Nota anche che * devi * mettere questo * alla fine * della sezione config, altrimenti avrai un errore –

6

Confermato che questo errore può anche essere causato da un timeout della transazione. Giusto per aggiungere quello che Marcus + Rolf ha dichiarato, se non hai impostato un timeout in modo esplicito su TransactionScope, il timeout di TimeSpan assumerà un valore predefinito. Questo valore predefinito è il più piccolo di:

  1. Se abbiate scavalcato l'impostazione locale app.config/web.config, per esempio

    <system.transactions> 
    <defaultSettings timeout="00:05:00" /> 
    </system.transactions> 
    
  2. Ma questo viene poi 'limitato' al machine.config impostazione <machineSettings maxTimeout="00:10:00" />

+0

Posso _override_ questo in * ** file app.config ***? – Kiquenet

1

Questa eccezione può anche essere causato da disabilitareMicrosoft Distributed Transaction Coordinator.

Se vogliamo abilitarlo, corriamo "dcomcnfg" e selezionare "Component Services" -> "My Computer" -> "Distributed Transaction Coordinator" -> "Local Service DTC" e scegli "Proprietà ".

dovrebbe essere controllato "Consenti Remote Client", "Consentire Inbound", "Consenti in uscita" e "Nessuna autenticazione richiesta".

+0

È possibile *** abilitare/disabilitare *** _programmaticamente_ utilizzando 'BAT' o' ps1'? – Kiquenet