API principales de logique de nouvelle tentative configurable dans SqlClient
S'applique à : .NET Framework .NET .NET Standard
Si les fournisseurs de logique de nouvelle tentative intégrés ne répondent pas à vos besoins, vous pouvez créer vos propres fournisseurs personnalisés. Vous pouvez ensuite affecter ces fournisseurs à un objet SqlConnection
ou SqlCommand
pour appliquer votre logique personnalisée.
Les fournisseurs intégrés sont conçus autour de trois interfaces qui peuvent être utilisées pour implémenter des fournisseurs personnalisés. Les fournisseurs de nouvelles tentatives personnalisés peuvent ensuite être utilisés de la même façon que les fournisseurs de nouvelles tentatives internes sur un SqlConnection ou un SqlCommand :
- SqlRetryIntervalBaseEnumerator : génère une séquence d’intervalles de temps.
- SqlRetryLogicBase : récupère le prochain intervalle de temps pour un énumérateur donné, si le nombre de nouvelles tentatives n’a pas été dépassé et qu’une condition temporaire est remplie.
- SqlRetryLogicBaseProvider : applique la logique de nouvelle tentative aux opérations de connexion et de commande.
Attention
En implémentant un fournisseur de logique de nouvelle tentative personnalisé, vous êtes responsable de tous les aspects, notamment l’accès concurrentiel, les performances et la gestion des exceptions.
Exemple
Dans cet exemple, l’implémentation est aussi simple que possible pour illustrer la personnalisation pas à pas. Elle n’inclut pas des pratiques avancées comme la cohérence de thread, la fonctionnalité Async et l’accès concurrentiel. Pour une présentation approfondie d’une implémentation réelle, vous pouvez étudier la logique de nouvelle tentative prédéfinie dans le dépôt GitHub Microsoft.Data.SqlClient.
Définissez des classes de logique de nouvelle tentative configurable personnalisées :
- Énumérateur : définissez une séquence fixe d’intervalles de temps et étendez la plage de temps acceptable de deux à quatre minutes.
public class CustomEnumerator : SqlRetryIntervalBaseEnumerator { // Set the maximum acceptable time to 4 minutes private readonly TimeSpan _maxValue = TimeSpan.FromMinutes(4); public CustomEnumerator(TimeSpan timeInterval, TimeSpan maxTime, TimeSpan minTime) : base(timeInterval, maxTime, minTime) {} // Return fixed time on each request protected override TimeSpan GetNextInterval() { return GapTimeInterval; } // Override the validate method with the new time range validation protected override void Validate(TimeSpan timeInterval, TimeSpan maxTimeInterval, TimeSpan minTimeInterval) { if (minTimeInterval < TimeSpan.Zero || minTimeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(minTimeInterval)); } if (maxTimeInterval < TimeSpan.Zero || maxTimeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(maxTimeInterval)); } if (timeInterval < TimeSpan.Zero || timeInterval > _maxValue) { throw new ArgumentOutOfRangeException(nameof(timeInterval)); } if (maxTimeInterval < minTimeInterval) { throw new ArgumentOutOfRangeException(nameof(minTimeInterval)); } } }
- Logique de nouvelle tentative : implémentez une logique de nouvelle tentative sur n’importe quelle commande qui ne fait pas partie d’une transaction active. Réduisez le nombre de nouvelles tentatives de 60 à 20.
public class CustomRetryLogic : SqlRetryLogicBase { // Maximum number of attempts private const int maxAttempts = 20; public CustomRetryLogic(int numberOfTries, SqlRetryIntervalBaseEnumerator enumerator, Predicate<Exception> transientPredicate) { if (!(numberOfTries > 0 && numberOfTries <= maxAttempts)) { // 'numberOfTries' should be between 1 and 20. throw new ArgumentOutOfRangeException(nameof(numberOfTries)); } // Assign parameters to the relevant properties NumberOfTries = numberOfTries; RetryIntervalEnumerator = enumerator; TransientPredicate = transientPredicate; Current = 0; } // Prepare this object for the next round public override void Reset() { Current = 0; RetryIntervalEnumerator.Reset(); } public override bool TryNextInterval(out TimeSpan intervalTime) { intervalTime = TimeSpan.Zero; // First try has occurred before starting the retry process. // Check if retry is still allowed bool result = Current < NumberOfTries - 1; if (result) { // Increase the number of attempts Current++; // It's okay if the RetryIntervalEnumerator gets to the last value before we've reached our maximum number of attempts. // MoveNext() will simply leave the enumerator on the final interval value and we will repeat that for the final attempts. RetryIntervalEnumerator.MoveNext(); // Receive the current time from enumerator intervalTime = RetryIntervalEnumerator.Current; } return result; } }
- Fournisseur : implémente un fournisseur de nouvelles tentatives qui effectue une nouvelle tentative sur des opérations synchrones sans événement
Retrying
. Ajoute TimeoutException aux numéros d’erreur d’exception temporaires SqlException existants.
public class CustomProvider : SqlRetryLogicBaseProvider { // Preserve the given retryLogic on creation public CustomProvider(SqlRetryLogicBase retryLogic) { RetryLogic = retryLogic; } public override TResult Execute<TResult>(object sender, Func<TResult> function) { // Create a list to save transient exceptions to report later if necessary IList<Exception> exceptions = new List<Exception>(); // Prepare it before reusing RetryLogic.Reset(); // Create an infinite loop to attempt the defined maximum number of tries do { try { // Try to invoke the function return function.Invoke(); } // Catch any type of exception for further investigation catch (Exception e) { // Ask the RetryLogic object if this exception is a transient error if (RetryLogic.TransientPredicate(e)) { // Add the exception to the list of exceptions we've retried on exceptions.Add(e); // Ask the RetryLogic for the next delay time before the next attempt to run the function if (RetryLogic.TryNextInterval(out TimeSpan gapTime)) { Console.WriteLine($"Wait for {gapTime} before next try"); // Wait before next attempt Thread.Sleep(gapTime); } else { // Number of attempts has exceeded the maximum number of tries throw new AggregateException("The number of retries has exceeded the maximum number of attempts.", exceptions); } } else { // If the exception wasn't a transient failure throw the original exception throw; } } } while (true); } public override Task<TResult> ExecuteAsync<TResult>(object sender, Func<Task<TResult>> function, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } public override Task ExecuteAsync(object sender, Func<Task> function, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } }
Créez une instance de fournisseur de nouvelles tentatives composée des types personnalisés définis :
public static SqlRetryLogicBaseProvider CreateCustomProvider(SqlRetryLogicOption options) { // 1. create an enumerator instance CustomEnumerator customEnumerator = new CustomEnumerator(options.DeltaTime, options.MaxTimeInterval, options.MinTimeInterval); // 2. Use the enumerator object to create a new RetryLogic instance CustomRetryLogic customRetryLogic = new CustomRetryLogic(5, customEnumerator, (e) => TransientErrorsCondition(e, options.TransientErrors)); // 3. Create a provider using the RetryLogic object CustomProvider customProvider = new CustomProvider(customRetryLogic); return customProvider; }
- La fonction suivante évaluera une exception en utilisant la liste donnée d’exceptions reproductibles et l’exception TimeoutException spéciale pour déterminer si elle peut être reproduite :
// Return true if the exception is a transient fault. private static bool TransientErrorsCondition(Exception e, IEnumerable<int> retriableConditions) { bool result = false; // Assess only SqlExceptions if (retriableConditions != null && e is SqlException ex) { foreach (SqlError item in ex.Errors) { // Check each error number to see if it is a retriable error number if (retriableConditions.Contains(item.Number)) { result = true; break; } } } // Other types of exceptions can also be assessed else if (e is TimeoutException) { result = true; } return result; }
Utilisez la logique de nouvelle tentative personnalisée :
- Définissez les paramètres de logique de nouvelle tentative :
// Define the retry logic parameters var options = new SqlRetryLogicOption() { // Tries 5 times before throwing an exception NumberOfTries = 5, // Preferred gap time to delay before retry DeltaTime = TimeSpan.FromSeconds(1), // Maximum gap time for each delay time before retry MaxTimeInterval = TimeSpan.FromSeconds(20), // SqlException retriable error numbers TransientErrors = new int[] { 4060, 1024, 1025} };
- Créez un fournisseur de nouvelles tentatives personnalisé :
// Create a custom retry logic provider SqlRetryLogicBaseProvider provider = CustomRetry.CreateCustomProvider(options);
- Affectez le fournisseur de nouvelles tentatives à SqlConnection.RetryLogicProvider ou à SqlCommand.RetryLogicProvider :
// Assumes that connection is a valid SqlConnection object // Set the retry logic provider on the connection instance connection.RetryLogicProvider = provider; // Establishing the connection will trigger retry if one of the given transient failure occurs. connection.Open();
Notes
N’oubliez pas d’activer le commutateur de logique de nouvelle tentative configurable avant de l’utiliser. Pour plus d’informations, consultez Activer la logique de nouvelle tentative configurable.