web-dev-qa-db-fra.com

Implémentation de la logique de nouvelle tentative pour les exceptions de blocage

J'ai implémenté un référentiel générique et je me demandais s'il existe un moyen intelligent d'implémenter une logique de nouvelle tentative en cas d'exception de blocage?

L'approche doit être la même pour toutes les méthodes de référentiel. Alors, est-ce que je peux de toute façon éviter d'écrire à nouveau "méthode try/catch - call avec retry-count", dans chaque méthode?

Toute suggestion est la bienvenue.

Un peu de mon code de référentiel:

public class GenericRepository : IRepository
{
    private ObjectContext _context;

    public List<TEntity> ExecuteStoreQuery<TEntity>(string commandText, params object[] parameters) where TEntity : class
    {
        List<TEntity> myList = new List<TEntity>();

        var groupData = _context.ExecuteStoreQuery<TEntity>(commandText, parameters);

        return myList;
    }


    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {          
        var entityName = GetEntityName<TEntity>();
        return _context.CreateQuery<TEntity>(entityName);
    }

    public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
    {
        return GetQuery<TEntity>().AsEnumerable();
    }

ÉDITER:

1.Solution:

Modifié légèrement de chris.house. solution

 public static T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
    {
        var retryCount = 0;

        while (retryCount < maxRetries)
        {
            try
            {
                return repositoryMethod();
            }
            catch (System.Data.SqlClient.SqlException ex)
            {
                if (ex.Number == 1205)// Deadlock                         
                    retryCount++;
                else
                    throw;                   
            }
        }
        return default(T);
    }

Et vous l'appelez comme ceci:

    public TEntity FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return RetryUtility.DeadlockRetryHelper<TEntity>( () =>p_FirstOrDefault<TEntity>(predicate), 3);
    }

    protected TEntity p_FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().FirstOrDefault<TEntity>(predicate);
    }
36
user1638662

Que diriez-vous quelque chose comme ça:

public T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
  int retryCount = 0;

  while (retryCount < maxRetries)
  {
    try
    {
      return repositoryMethod();
    }
    catch (SqlException e) // This example is for SQL Server, change the exception type/logic if you're using another DBMS
    {
      if (e.Number == 1205)  // SQL Server error code for deadlock
      {
        retryCount++;
      }
      else
      {
        throw;  // Not a deadlock so throw the exception
      }
      // Add some code to do whatever you want with the exception once you've exceeded the max. retries
    }
  }
}

Avec le code ci-dessus, votre logique de nouvelle tentative est entièrement dans cette méthode et vous pouvez simplement passer votre méthode de référentiel en tant que délégué.

34
chris.house.00

Je sais que c'est un ancien message mais je voulais partager une réponse mise à jour.

EF 6 a maintenant une solution intégrée, vous pouvez définir la stratégie d'exécution qui serait une implémentation unique. Vous créez une classe qui hérite de DbExectutionStrategy et remplace la méthode virtuelle ShouldRetryOn. Vous pouvez créer une classe statique des exceptions contenant des valeurs de champ constantes qui sont des codes éligibles de nouvelle tentative et parcourir chacune d'elles pour déterminer si l'exception sql actuelle levée correspond à la liste des codes de nouvelle tentative éligibles ...

 public static class SqlRetryErrorCodes
{
    public const int TimeoutExpired = -2;
    public const int Deadlock = 1205;
    public const int CouldNotOpenConnection = 53;
    public const int TransportFail = 121;
}

public class MyCustomExecutionStrategy : DbExecutionStrategy
{
    public MyCustomExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay) { }

     private readonly List<int> _errorCodesToRetry = new List<int>
    {
        SqlRetryErrorCodes.Deadlock,
        SqlRetryErrorCodes.TimeoutExpired,
        SqlRetryErrorCodes.CouldNotOpenConnection,
        SqlRetryErrorCodes.TransportFail
    };
    protected override bool ShouldRetryOn(Exception exception)
    {
        var sqlException = exception as SqlException;
        if (sqlException != null)
        {
            foreach (SqlError err in sqlException.Errors)
            {
                // Enumerate through all errors found in the exception.
                if (_errorCodesToRetry.Contains(err.Number))
                {
                    return true;
                }
            }
        }
        return false;
    }
}

Enfin, une fois que vous avez configuré votre stratégie d'exécution personnalisée, vous créez simplement une autre classe qui hérite de DbConfiguration avec un constructeur public qui définit la stratégie d'exécution:

 public class MyEfConfigurations : DbConfiguration
    {
        public MyEfConfigurations()
        {
            SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
        }
    }
28
CodeAlchemist

EntityFramework 6 ajouter ExecutionStrategy fonction. Il suffit de configurer correctement la stratégie.

Ma politique de nouvelle tentative:

public class EFRetryPolicy : DbExecutionStrategy
{
    public EFRetryPolicy() : base()
    {
    }
    //Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
    public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
    {
    }
    protected override bool ShouldRetryOn(Exception ex)
    {

        bool retry = false;

        SqlException sqlException = ex as SqlException;
        if (sqlException != null)
        {
            int[] errorsToRetry =
            {
                1205,  //Deadlock
                -2,    //Timeout
            };
            if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
            {
                retry = true;
            }

        }          
        return retry;
    }
}

Dites à EF d'appliquer ma stratégie:

public class EFPolicy: DbConfiguration
{
    public EFPolicy()
    {
        SetExecutionStrategy(
            "System.Data.SqlClient",
            () => new EFRetryPolicy());
    }
}

Sources:

La stratégie de nouvelle tentative ne fonctionnera pas avec les transactions initiées par l'utilisateur (transaction créée avec TransactionScope) comme expliqué ici . Si utilisé, vous obtiendrez l'erreur The configured execution strategy does not support user initiated transactions

5
MiguelSlv

La solution fonctionne bien que je préfère ne pas avoir à me soucier du nombre d'arguments du Action ou Func qui seront retirés. Si vous créez une méthode de nouvelle tentative avec un Action générique, vous pouvez gérer toute la variabilité de la méthode à appeler dans un lambda:

public static class RetryHelper
{

    public static void DeadlockRetryHelper(Action method, int maxRetries = 3)
    {
        var retryCount = 0;

        while (retryCount < maxRetries)
        {
            try
            {
                method();
                return;
            }
            catch (System.Data.SqlClient.SqlException ex)
            {
                if (ex.Number == 1205)// Deadlock           
                {
                    retryCount++;
                    if (retryCount >= maxRetries)
                        throw;
                    // Wait between 1 and 5 seconds
                    Thread.Sleep(new Random().Next(1000, 5000));
                }
                else
                    throw;
            }
        }

    }
}

Ensuite, utilisez-le comme ceci:

RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));
1
Josh Russo

Avez-vous envisagé une forme quelconque d'injection de politique? Vous pouvez utiliser l'interception Unity, à titre d'exemple, pour capturer tous vos appels de référentiel. Ensuite, vous écrivez simplement la logique de nouvelle tentative, dans l'intercepteur, plutôt que de la répéter plusieurs fois dans chaque méthode.

1
Sean H