web-dev-qa-db-fra.com

TransactionScope terminé 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électionne, met à jour, crée et supprime toute la gamme. Lorsque j'exécute ma suppression, je l'exécute à l'aide d'une méthode d'extension de SqlCommand qui soumettra automatiquement la requête à nouveau si elle se bloque car cette requête pourrait potentiellement aboutir à un blocage.

Je crois 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é supprimée. La transaction doit être supprimé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 l'utilisation de 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();
64
Chris

N'oubliez pas de supprimer vos relevés sélectionnés de votre TransactionScope. Dans SQL Server 2005 et versions ultérieures, même lorsque vous utilisez avec (nolock), des verrous sont toujours créés sur les tables que vous sélectionnez. Vérifiez cela, il 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 
  } 
} 
60
Dan

J'ai constaté que ce message peut se produire lorsqu'une transaction s'exécute pendant une période plus longue que maxTimeout pour System.Transactions. Peu importe que TransactionOptions.Timeout est augmenté, il ne peut pas dépasser maxTimeout.

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

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

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

Le fichier machine.config se trouve sur: %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

41
Marcus

Je peux reproduire le problème. Il s'agit d'un délai d'attente 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())
                    {
                    }
                }
            }
        }
    }
}

Jette System.InvalidOperationException avec ce message:

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

Pour résoudre le problème, accélérez l'exécution de votre requête ou augmentez le délai d'expiration.

21
Rolf

Si une exception se produit dans un TransactionScope, elle est annulée. Cela signifie que TransactionScope est terminé. Vous devez maintenant appeler dispose() dessus et démarrer une nouvelle transaction. Honnêtement, je ne sais pas si vous pouvez réutiliser l'ancien TransactionScope ou non, je n'ai jamais essayé, mais je suppose que non.

11
Donnie

Mon problème était stupide, si vous vous asseyez sur une pause de débogage pendant le délai d'attente, vous obtiendrez ceci. Face Palm

Mec, la programmation vous fait vous sentir épaisse certains jours ...

6
Samuel Fleming

Confirmé que cette erreur peut également être causée par un délai d'attente de transaction. Juste pour ajouter à ce que Marcus + Rolf a déclaré, si vous n'avez pas explicitement défini un délai d'expiration sur le TransactionScope, le délai d'expiration TimeSpan prendra une valeur par défaut. Cette valeur par défaut est le plus petit de:

  1. Si vous avez remplacé le paramètre local app.config/web.config, Par exemple.

    <system.transactions>
    <defaultSettings timeout="00:05:00" />
    </system.transactions>
    
  2. Mais ceci est alors "plafonné" au paramètre machine.config<machineSettings maxTimeout="00:10:00" />

6
StuartLC

Cette exception peut également être provoquée par désactiverMicrosoft Distributed Transaction Coordinator.

Si nous voulons l'activer, nous exécutons "dcomcnfg" et sélectionnez "Component Services" -> "My Computer" -> "Distributed Transaction Coordinator" -> "Local Service DTC" et choisissez "Propriétés".

Elle doit être cochée "Autoriser le client distant", "Autoriser les appels entrants", "Autoriser les appels sortants" et "Aucune authentification requise ".

1
Adrian Tarnowski