65 votes

TransactionScope achevé prématurément

J'ai un bloc de code qui s'exécute dans un TransactionScope et dans ce bloc de code, je fais plusieurs appels à la base de données. Sélections, mises à jour, créations et suppressions, toute la gamme. Lorsque j'exécute ma suppression, je l'exécute en utilisant une méthode d'extension de la SqlCommand qui soumettra à nouveau automatiquement la requête en cas de blocage, car cette requête peut potentiellement se bloquer.

Je pense que le problème se produit lorsqu'un blocage est atteint et que la fonction tente de soumettre à nouveau la requête. Voici l'erreur que je reçois :

La transaction associée à la connexion actuelle est terminée mais n'a pas été cédée. La transaction doit être éliminée avant que la connexion puisse être utilisée pour exécuter des instructions SQL.

Il s'agit du code simple qui exécute la requête (tout le code ci-dessous s'exécute dans le cadre de l'utilisation du TransactionScope) :

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

Voici la méthode d'extension qui soumet à nouveau la requête bloquée :

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'erreur se produit sur la ligne :

sqlCommand.ExecuteNonQuery();

61voto

Dan Points 706

N'oubliez pas de supprimer vos déclarations de sélection de votre TransactionScope. Dans SQL Server 2005 et supérieur, même lorsque vous utilisez with(nolock), des verrous sont toujours créés sur les tables que la sélection touche. Regardez ceci, cela vous montre comment configurer et utiliser 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 votes

Merci pour le conseil sur la suppression des transactions sur les instructions de sélection. Cela a permis de résoudre un problème de délai d'attente qui me rendait fou.

2 votes

Réponse fantastique. Cela me rendait fou sur une collection de sélection/insertion d'instructions sql. L'ajout de l'option de suppression résout automatiquement le problème.

1 votes

Bon sang, merci ! Je me suis débattu avec ça toute la journée. Une solution si simple.

42voto

Marcus Points 1348

J'ai constaté que ce message peut se produire lorsqu'une transaction s'exécute pendant une période plus longue que celle prévue par l'accord. maxTimeout pour System.Transactions . Cela n'a pas d'importance que TransactionOptions.Timeout est augmenté, il ne peut pas dépasser maxTimeout .

La valeur par défaut de maxTimeout est fixée à 10 minutes et sa valeur peut uniquement être modifié dans le machine.config

Ajoutez ce qui suit (au niveau de la configuration) au fichier machine.config pour modifier le délai d'attente :

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

Le fichier machine.config se trouve à l'adresse suivante %windir%\Microsoft.NET\Framework\[version]\config\machine.config

Vous pouvez en savoir plus à ce sujet dans cet article de blog : http://thecodesaysitall.blogspot.se/2012/04/long-running-systemtransactions.html

4 votes

Gardez à l'esprit que le fichier de configuration est sensible aux majuscules et minuscules et qu'il doit s'agir de "machineSettings" et "maxTimeout". Dommage que vous ne puissiez pas modifier ces paramètres dans votre fichier app.config :(

1 votes

Notez également que vous doit mettre cela à la fin de la section config, sinon vous obtiendrez une erreur.

22voto

Rolf Points 165

Je peux reproduire le problème. Il s'agit d'un délai de transaction.

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

Lance System.InvalidOperationException avec ce message : "La transaction associée à la connexion actuelle s'est terminée mais n'a pas été éliminée. La transaction doit être éliminée avant que la connexion puisse être utilisée pour exécuter des instructions SQL."

Pour résoudre ce problème, il faut accélérer l'exécution de la requête ou augmenter le délai d'attente.

11voto

Donnie Points 17312

Si une exception se produit à l'intérieur d'un TransactionScope il est annulé. Cela signifie que TransactionScope est terminée. Vous devez maintenant appeler dispose() sur elle et commencer une nouvelle transaction. Honnêtement, je ne suis pas sûr que vous puissiez réutiliser l'ancienne TransactionScope ou pas, je n'ai jamais essayé, mais je suppose que non.

2 votes

Même si l'exception est levée, la transaction est annulée ?

0 votes

Je ne l'ai jamais expérimenté car pour moi, exception = erreur = arrêt et retour en arrière. Cependant, il semble que ce soit le cas d'après ce que vous décrivez.

2 votes

C'est faux, et ce n'est pas ce qui se passe pour les exceptions. Vous n'avez pas non plus à appeler Dispose() dans ce cas. Lorsque le TransactionScope est généré dans une instruction using, l'instruction using va Dispose() le TransactionScope sur les exceptions.

7voto

Samuel Fleming Points 104

Mon problème était stupide, si vous restez sur une pause de débogage pendant le délai d'attente, vous obtiendrez ceci. Paume du visage

Mec, la programmation te fait sentir épais certains jours...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X