C# 实现死锁异常的重试逻辑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13159326/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 01:48:54  来源:igfitidea点击:

Implementing retry logic for deadlock exceptions

c#entity-frameworktry-catchrepository-patterndatabase-deadlocks

提问by user1638662

I've implemented a generic repository and was wondering if there is a smart way to implement a retry logic in case of a deadlock exception?

我已经实现了一个通用存储库,想知道是否有一种聪明的方法来实现死锁异常时的重试逻辑?

The approach should be the same for all repository methods. So is there anyway I can avoid writing 'try/catch - call method again with retry-count', in every single method?

对于所有存储库方法,该方法应该相同。那么无论如何我可以避免在每个方法中编写“尝试/捕获 - 再次使用重试计数调用方法”?

Any suggetsion are welcome.

欢迎任何建议。

A bit of my Repository code:

一些我的存储库代码:

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

EDIT:

编辑:

1.Solution:

1.解决方法:

Modified slightly fromchris.house.00's 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);
    }

And you call it like this:

你这样称呼它:

    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);
    }

采纳答案by chris.house.00

How about something like this:

这样的事情怎么样:

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
    }
  }
}

With the above code, your retry logic is all in this method and you can just pass your repository method in as a delegate.

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

回答by Sean H

Have you considered some form of policy injection? You could use Unity interception, just as an example, to capture all your repository calls. Then you just write the retry logic once, in the interceptor, rather than repeating it many times in each method.

您是否考虑过某种形式的政策注入?作为示例,您可以使用 Unity 拦截来捕获所有存储库调用。然后你只需在拦截器中编写一次重试逻辑,而不是在每个方法中重复多次。

回答by CodeAlchemist

I know this is an old post but wanted to share an updated answer.

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

EF 6 now has a built-in solution, you can set the execution strategy which would be a one time implementation. You create a class that inherits from DbExectutionStrategy and overrides the ShouldRetryOn virtual method. You can create a static class of the exceptions containing constant field valuess that are retry eligible codes and loop through each one to determine if the current sql exception being thrown matches the list of eligible retry codes...

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;
    }
}

Finally once, you've set up your custom execution strategy, you simply create another class that inherits from DbConfiguration with a public constructor that Sets the execution strategy:

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

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

回答by Josh Russo

The solution works though I prefer not to have to worry about the number of arguments to the Actionor Functhat will be retired. If you create a single retry method with a generic Action, you can handle all of the variability of the method to be called in a lambda:

该解决方案有效,但我不希望不必担心ActionFunc将被淘汰的参数数量。如果您使用泛型创建单个重试方法Action,则可以处理要在 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;
            }
        }

    }
}

Then use it like so:

然后像这样使用它:

RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));

回答by MiguelSlv

EntityFramework 6add ExecutionStrategyfeature. All that is need is to setup up the strategy properly.

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

My retry policy:

我的重试策略:

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;
    }
}

Tell EF to apply my strategy:

告诉 EF 应用我的策略:

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

Sources:

资料来源:

The retry strategy will not work with user initiated transactions (transaction created with TransactionScope) as explained here. If used you will get the Error The configured execution strategy does not support user initiated transactions

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