Реализация логики повтора для исключений блокировки
Я реализовал общий репозиторий и задался вопросом, есть ли разумный способ реализовать логику повтора в случае исключения тупика?
Подход должен быть одинаковым для всех методов репозитория. Так или иначе, я могу избежать повторного использования метода try/catch-call с помощью параметра retry-count, в каждом отдельном методе?
Любое предложение приветствуется.
Немного моего кода репозитория:
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();
}
ИЗМЕНИТЬ:
1.Решение:
Изменено немного от решения 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);
}
И вы называете это следующим образом:
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);
}
Ответы
Ответ 1
Как насчет чего-то вроде этого:
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
}
}
}
С приведенным выше кодом логика повторения находится в этом методе, и вы можете просто передать свой метод репозитория в качестве делегата.
Ответ 2
Я знаю, что это старый пост, но хотел поделиться обновленным ответом.
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;
}
}
Наконец, вы настроили свою стратегию выполнения, вы просто создаете другой класс, который наследует от DbConfiguration публичный конструктор, который устанавливает стратегию выполнения:
public class MyEfConfigurations : DbConfiguration
{
public MyEfConfigurations()
{
SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
}
}
Ответ 3
Рассматривали ли вы какую-то форму введения политики? Вы можете использовать перехват Unity, как пример, для захвата всех вызовов вашего репозитория. Затем вы просто пишете логику повтора один раз в перехватчике, а не повторяете его много раз в каждом методе.
Ответ 4
Решение работает, хотя я предпочитаю не беспокоиться о количестве аргументов для Action
или Func
, которые будут удалены. Если вы создаете один метод повторения с общим Action
, вы можете обрабатывать всю изменчивость метода, который будет вызываться в лямбда:
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;
}
}
}
}
Затем используйте его так:
RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));
Ответ 5
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;
}
}
Скажите EF, чтобы применить мою стратегию:
public class EFPolicy: DbConfiguration
{
public EFPolicy()
{
SetExecutionStrategy(
"System.Data.SqlClient",
() => new EFRetryPolicy());
}
}
Источники:
Стратегия повтора не будет работать с инициированными пользователем транзакциями (транзакция, созданная с помощью TransactionScope
), как описано здесь. При использовании вы получите сообщение об ошибке The configured execution strategy does not support user initiated transactions