C# + SQL : Gestion des deadLocks

Lorsque l'on execute une requête sur une base de donnée il peut arriver qu'une requête échoue suite à blocage des données que l'on veut modifier.

SQL Server, dans le cas d'un accès concurrent va sélectionner d'un des deux processus qui tente de modifier les données et le fermer.

Il existe de nombreuses solutions différentes sur le web, modifier les requêtes pour rajouter une gestion des priorités (DEADLOCK_PRIORITY), utiliser l'attribut (NOLOCK) ...

Une solution consiste à réexécuter la requête qui a été "deadlockée". Voici donc quelques méthodes qui permettent, lorsque l'exécution d'une requête génère une erreur de la rexécutée si l'erreur est dûe à un deadlock. On peut paramétrer le nombre de rexécutions ainsi que le temps d'attente entre chaque essai.

Ces 3 méthodes permettent de gérer les 3 méthodes de l'objet SQLCommand : ExecuteReader(), ExecuteScalar() et ExecuteNonQuery().

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using System.Drawing;

using System.Text;

using System.Xml;

using System.IO;

using System.Reflection;

using System.Collections;

using System.Threading;

 

namespace matac.sql

{

    /// <summary>

    /// Classe permettant de lancer des commandes Sql et d'avoir une gestion des Erreurs et notamment des DeadLocks.

    /// </summary>

    public sealed class SqlCommandExt

    {

        private SqlCommandExt()

        {

 

        }

 

        /// <summary>

        /// Permet d'exectuer une commande et renvoyer un DataReader associé.

        /// En cas de DeadLock (accès concurent) la requête est réexecutée après une attente de 250ms.

        /// </summary>

        /// <param name="sqlComm">Commande à executer.</param>

        /// <param name="sqlData">DataReader à utiliser.</param>

        /// <param name="booSingleRow">Indique si une seule ligne est attendue :</param>

        /// <param name="intCountTry">Nombre d'essais avant d'arréter</param>

        /// <returns>Booléen indiquant si la commande a bien été executée.</returns>

        public static String ExecuteDataReader(SqlCommand sqlComm, ref SqlDataReader sqlData, Boolean booSingleRow, Int32 intCountTry)

        {

            Int32 intCount = intCountTry;

            Boolean boo = false;

            String strResult = "";

 

            while (!boo && intCount >= 0)

            {

                intCount--;

                try

                {

                    if (booSingleRow)

                        sqlData = sqlComm.ExecuteReader(CommandBehavior.SingleRow);

                    else

                        sqlData = sqlComm.ExecuteReader();

                    boo = true;

                }

                catch (SqlException ex)

                {

                    if (ex.Number == 1205 && intCount >= 0)  // DeadLock

                        Thread.Sleep(250);

                    else    // Terminé

                    {

                        boo = false;

                        strResult = ex.Message;

                    }

                }

                catch (InvalidOperationException ex)

                {

                    // Terminé.

                    boo = false;

                    strResult = ex.Message;

                }

                catch (Exception ex)

                {

                    // Terminé.

                    boo = false;

                    strResult = ex.Message;

                }

            }

 

            return strResult;

        }

 

        /// <summary>

        /// Permet d'exectuer une commande sql.

        /// En cas de DeadLock (accès concurent) la requête est réexecutée après une attente de 250ms.

        /// </summary>

        /// <param name="sqlComm">Commande à executer.</param>

        /// <param name="intCountTry">Nombre d'essais avant d'arréter</param>

        /// <returns>Chaine de caractère contenant l'erreur générée, rien si tout s'est bien passé.</returns>

        public static String ExecuteNonQuery(SqlCommand sqlComm, Int32 intCountTry)

        {

            Int32 intCount = intCountTry;

            Boolean boo = false;

            String strResult = "";

            while (!boo && intCount >= 0)

            {

                intCount--;

                try

                {

                    sqlComm.ExecuteNonQuery();

                    boo = true;

                }

                catch (SqlException ex)

                {

                    if (ex.Number == 1205 && intCount >= 0)  // DeadLock

                        Thread.Sleep(250);

                    else

                    {

                        boo = false;

                        strResult = ex.Message;

                    }

                }

                catch (Exception ex)

                {

                    // Terminé, une erreur qui n'est pas du type SqlException a été levée.

                    boo = false;

                    strResult = ex.Message;

                }

            }

 

            return strResult;

        }

 

        /// <summary>

        /// Permet d'exectuer une commande sql.

        /// En cas de DeadLock (accès concurent) la requête est réexecutée après une attente de 250ms.

        /// </summary>

        /// <param name="sqlComm">Commande à executer.</param>

        /// <param name="objResult">Résultat de la requête.</param>

        /// <param name="intCountTry">Nombre d'essais avant d'arréter</param>

        /// <returns>Booléen indiquant si la commande a bien été executée.</returns>

        public static Boolean ExecuteScalar<T>(SqlCommand sqlComm, ref T objResult, Int32 intCountTry)

        {

            Int32 intCount = intCountTry;

            Boolean boo = false;

 

            while (!boo && intCount >= 0)

            {

                intCount--;

                try

                {

                    objResult = (T)sqlComm.ExecuteScalar();

                    boo = true;

                }

                catch (SqlException ex)

                {

                    if (ex.Number == 1205 && intCount >= 0)  // DeadLock

                        Thread.Sleep(250);

                    else    // Terminé

                        boo = false;

                }

                catch (Exception)

                {

                    // Terminé

                    boo = false;

                }

            }

 

            return boo;

        }

    }

}