实现死锁异常的重试逻辑

use*_*662 33 c# entity-framework try-catch repository-pattern database-deadlocks

我已经实现了一个通用存储库,并想知道是否有一种智能方法可以在出现死锁异常的情况下实现重试逻辑?

所有存储库方法的方法都应该相同.那么无论如何我可以避免在每一种方法中使用retry-count再次编写'try/catch - call方法'吗?

任何建议都是受欢迎的.

我的存储库代码:

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();
    }
Run Code Online (Sandbox Code Playgroud)

编辑:

1.Solution:

chris.house.00解决方案略微修改

 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);
    }
Run Code Online (Sandbox Code Playgroud)

你这样称呼它:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

chr*_*.00 34

这样的事情怎么样:

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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

使用上面的代码,您的重试逻辑都在此方法中,您可以将您的存储库方法作为委托传递.

  • 使用C#6`catch(SqlException ex)时(ex.Number == 1205)` (11认同)
  • 您应该在重试之间暂停一下,以便让锁定时间释放其锁定.您还应该暂停一段随机间隔,以防止相互死锁同时重试并再次死锁*. (4认同)
  • 如果耗尽重试次数,则函数将返回而不会抛出异常. (2认同)

Cod*_*ist 26

我知道这是一篇旧帖,但想分享一个更新的答案.

EF 6现在有一个内置的解决方案,您可以设置执行策略,这将是一次性实现.您创建一个继承自DbExectutionStrategy的类并重写ShouldRetryOn虚方法.您可以创建一个包含常量字段值的异常的静态类,它们是重试符合条件的代码并循环遍历每个异常,以确定抛出的当前sql异常是否与符合条件的重试代码列表匹配...

 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后一次,您已经设置了自定义执行策略,您只需创建另一个继承自DbConfiguration的类,该类具有设置执行策略的公共构造函数:

 public class MyEfConfigurations : DbConfiguration
    {
        public MyEfConfigurations()
        {
            SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
        }
    }
Run Code Online (Sandbox Code Playgroud)


Mig*_*Slv 8

EntityFramework 6 添加ExecutionStrategy功能。所需要的只是正确设置策略。

我的重试策略:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

告诉 EF 应用我的策略:

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

资料来源:

重试策略不会有用户发起交易(与创建的事务工作TransactionScope)作为解释在这里。如果使用,您将收到错误The configured execution strategy does not support user initiated transactions