tipos sistemas sincronizacion semaforos propiedades procesos operativos mutua los exclusion eleccion distribuidos centralizado algoritmos algoritmo c# entity-framework try-catch repository-pattern database-deadlocks

c# - sistemas - sincronizacion de procesos semaforos



Implementando la lógica de reintento para excepciones de interbloqueo (5)

He implementado un repositorio genérico y me preguntaba si hay una manera inteligente de implementar una lógica de reintento en caso de una excepción de interbloqueo.

El enfoque debe ser el mismo para todos los métodos del repositorio. Entonces, ¿puedo evitar escribir de nuevo el método ''probar / atrapar - llamar con el conteo de reintentos'' en cada método?

Cualquier sugerencia es bienvenida.

Un poco de mi código de repositorio:

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

EDITAR:

1.solución:

Modificado ligeramente de la solución de 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); }

Y lo llamas así:

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


¿Ha considerado alguna forma de inyección política? Podría usar la intercepción de Unity, solo como ejemplo, para capturar todas sus llamadas al repositorio. Luego simplemente escribe la lógica de reintento una vez, en el interceptor, en lugar de repetirla muchas veces en cada método.


La solución funciona, aunque prefiero no tener que preocuparme por la cantidad de argumentos para la Action o la Func que se retirarán. Si creas un solo método de reintento con una Action genérica, puedes manejar toda la variabilidad del método que se llamará en 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; } } } }

Entonces utilízalo así:

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


Qué tal algo como esto:

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

Con el código anterior, su lógica de reintento está en este método y puede pasar su método de repositorio como delegado.


Sé que esta es una publicación antigua pero quería compartir una respuesta actualizada.

EF 6 ahora tiene una solución integrada, puede establecer la estrategia de ejecución que sería una implementación única. Crea una clase que hereda de DbExectutionStrategy y reemplaza el método virtual ShouldRetryOn. Puede crear una clase estática de las excepciones que contienen valores constantes de campo que son códigos elegibles de reintento y recorrer cada uno de ellos para determinar si la excepción de sql actual que se está lanzando coincide con la lista de códigos de reintento elegibles ...

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

Finalmente, una vez que haya configurado su estrategia de ejecución personalizada, simplemente cree otra clase que hereda de DbConfiguration con un constructor público que establece la estrategia de ejecución:

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


EntityFramework 6 agrega la característica ExecutionStrategy . Todo lo que se necesita es configurar la estrategia correctamente.

Mi política de reintento:

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

Dile a EF que aplique mi estrategia:

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

Fuentes:

La estrategia de reintento no funcionará con transacciones iniciadas por el usuario (transacción creada con TransactionScope ) como se explica here . Si se usa, obtendrá el error. The configured execution strategy does not support user initiated transactions