SqlClient の構成可能な再試行ロジックのコア API

適用対象: .NET Framework .NET .NET Standard

ADO.NET のダウンロード

組み込みの再試行ロジック プロバイダーではニーズに対応できない場合、独自のカスタム プロバイダーを作成できます。 その後、これらのプロバイダーを SqlConnection または SqlCommand オブジェクトに割り当てて、カスタム ロジックを適用できます。

組み込みプロバイダーは、カスタム プロバイダーの実装に使用できる 3 つのインターフェイスを中心に設計されています。 カスタム再試行プロバイダーは、SqlConnection または SqlCommand の内部再試行プロバイダーと同じ方法で使用できます。

  1. SqlRetryIntervalBaseEnumerator: 時間間隔のシーケンスを生成します。
  2. SqlRetryLogicBase: 再試行回数を超えておらず、一時的な条件が満たされた場合に、指定された列挙子の次の時間間隔を取得します。
  3. SqlRetryLogicBaseProvider: 接続およびコマンド操作に再試行ロジックを適用します。

注意事項

カスタムの再試行ロジック プロバイダーを実装することにより、コンカレンシー、パフォーマンス、例外管理など、すべての側面を管理できます。

このサンプルの実装は、カスタマイズの手順を示すためのものであり、可能な限りシンプルに作成されています。 スレッド セーフ、非同期、コンカレンシーなどの高度な手法は含まれていません。 実際の実装の詳細については、 Microsoft.Data.SqlClient GitHub リポジトリの事前定義済み再試行ロジックを参照してください。

  1. カスタムの構成可能な再試行ロジックのクラスを定義します。

    • 列挙子: 時間間隔の固定シーケンスを定義し、許容可能な時間の範囲を 2 分から 4 分に延長します。
    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));
            }
        }
    }
    
    • 再試行ロジック: アクティブなトランザクションの一部ではない任意のコマンドに再試行ロジックを実装します。 再試行回数を 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;
        }
    }
    
    • プロバイダー: Retrying イベント発生させずに同期操作を再試行する再試行プロバイダーを実装します。 既存の SqlException 一時例外エラー番号に TimeoutException を追加します。
    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();
        }
    }
    
  2. 定義されたカスタム型で構成される再試行プロバイダー インスタンスを作成します。

    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;
    }
    
    • 次の関数は、指定された再試行可能な例外のリストと特別な TimeoutException 例外を使用して例外を評価し、再試行可能かどうかを判断します。
    // 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;
    }
    
  3. カスタマイズされた再試行ロジックを使用します。

    • 再試行ロジック パラメーターを定義します。
    // 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}
    };
    
    • カスタムの再試行プロバイダーを作成します。
    // Create a custom retry logic provider
    SqlRetryLogicBaseProvider provider = CustomRetry.CreateCustomProvider(options);
    
    // 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();
    

Note

構成可能な再試行ロジックを使用する前に、そのスイッチを忘れずに有効にしてください。 詳細については、「構成可能な再試行ロジックを有効にする」を参照してください。

関連項目