다음을 통해 공유


SqlCommand.RetryLogicProvider 속성

정의

이 명령에 바인딩된 SqlRetryLogicBaseProvider 개체를 지정하는 값을 가져오거나 설정합니다.

public:
 property Microsoft::Data::SqlClient::SqlRetryLogicBaseProvider ^ RetryLogicProvider { Microsoft::Data::SqlClient::SqlRetryLogicBaseProvider ^ get(); void set(Microsoft::Data::SqlClient::SqlRetryLogicBaseProvider ^ value); };
[System.ComponentModel.Browsable(false)]
public Microsoft.Data.SqlClient.SqlRetryLogicBaseProvider RetryLogicProvider { get; set; }
public Microsoft.Data.SqlClient.SqlRetryLogicBaseProvider RetryLogicProvider { get; set; }
[<System.ComponentModel.Browsable(false)>]
member this.RetryLogicProvider : Microsoft.Data.SqlClient.SqlRetryLogicBaseProvider with get, set
member this.RetryLogicProvider : Microsoft.Data.SqlClient.SqlRetryLogicBaseProvider with get, set
Public Property RetryLogicProvider As SqlRetryLogicBaseProvider

속성 값

null(기본값)으로 설정하면 다시 시도 불가능한 기본 공급자가 사용됩니다.

특성

설명

이 속성을 적용하려면 명령을 실행하기 전에 이 속성의 값을 설정해야 합니다.

다시 시도 논리를 적용하려면 명령을 실행하기 전에 다음 단계를 수행합니다.

  1. 형식을 사용하여 구성 매개 변수를 SqlRetryLogicOption 정의합니다.
  2. 클래스의 SqlRetryLogicBaseProvider 다음 정적 메서드 중 하나를 사용하여 을 만듭니다 SqlConfigurableRetryFactory .
  3. 개체를 SqlRetryLogicBaseProvider 속성에 할당합니다 RetryLogicProvider .

참고

다시 시도 가능한 예외를 검색하는 것은 재시도 패턴의 중요한 부분입니다. 재시도 논리를 적용하기 전에 예외를 조사하고 시나리오에 가장 적합한 재시도 공급자를 선택하는 것이 중요합니다. 먼저 예외를 기록하고 일시적인 오류를 찾습니다.

참고

재시도 논리 내에서 명령을 실행할 때마다 다시 시도 시간 지연을 적용한 후 명령 시간 제한이 다시 시작됩니다. 이 두 작업 간에는 타이밍이 겹치지 않습니다.

참고

기본 재시도 논리 공급자는 애플리케이션 구성 파일에서 구성되지 않는 한 사용하도록 설정되지 않습니다. 자세한 내용은 구성 가능한 다시 시도 논리 구성 파일을 참조하세요.

주의

를 사용하는 CommandBehavior.CloseConnection 명령은 기본 제공 재시도 논리와 호환되지 않습니다. 기본 연결은 첫 번째 실행 시도 후 즉시 닫혀 있으며 후속 재시도에 더 이상 사용할 수 없습니다.

예제

다음 샘플에서는 데이터베이스를 만들고 데이터베이스에 대한 활성 연결을 설정합니다. 데이터베이스에는 활성 연결이 있지만 를 사용하는 새 SqlConnection 및 을 SqlCommand 사용하여 SqlRetryLogicBaseProvider삭제하려고 합니다. 재시도 횟수를 초과하기 전에 데이터베이스를 통해 활성 연결을 종료하여 두 번째 명령의 차단을 해제해야 합니다.
차단 연결은 데이터베이스에서 실행 중인 명령과 같은 상황을 시뮬레이션하며 완료될 가능성은 낮습니다.

/// Detecting retriable exceptions is a vital part of the retry pattern.
/// Before applying retry logic it is important to investigate exceptions and choose a retry provider that best fits your scenario.
/// First, log your exceptions and find transient faults.
/// The purpose of this sample is to illustrate how to use this feature and the condition might not be realistic.

    private const string DefaultDB = "Northwind";
    private const string CnnStringFormat = "Server=localhost; Initial Catalog={0}; Integrated Security=true; pooling=false;";
    private const string DropDatabaseFormat = "DROP DATABASE {0}";
    private const string CreateDatabaseFormat = "CREATE DATABASE {0}";

    // For general use
    private static SqlConnection s_generalConnection = new SqlConnection(string.Format(CnnStringFormat, DefaultDB));

    static void Main(string[] args)
    {
        // 1. Define the retry logic parameters
        var options = new SqlRetryLogicOption()
        {
            NumberOfTries = 5,
            MaxTimeInterval = TimeSpan.FromSeconds(20),
            DeltaTime = TimeSpan.FromSeconds(1),
            AuthorizedSqlCondition = null,
            // error number 3702 : Cannot drop database "xxx" because it is currently in use.
            TransientErrors = new int[] {3702}
        };

        // 2. Create a retry provider
        var provider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options);

        // define the retrying event to report execution attempts
        provider.Retrying += (object s, SqlRetryingEventArgs e) =>
            {
                int attempts = e.RetryCount + 1;
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine($"attempt {attempts} - current delay time:{e.Delay} \n");
                Console.ForegroundColor = ConsoleColor.DarkGray;
                if (e.Exceptions[e.Exceptions.Count - 1] is SqlException ex)
                {
                    Console.WriteLine($"{ex.Number}-{ex.Message}\n");
                }
                else
                {
                    Console.WriteLine($"{e.Exceptions[e.Exceptions.Count - 1].Message}\n");
                }

                // It is not good practice to do time-consuming tasks inside the retrying event which blocks the running task.
                // Use parallel programming patterns to mitigate it.
                if (e.RetryCount == provider.RetryLogic.NumberOfTries - 1)
                {
                    Console.WriteLine("This is the last chance to execute the command before throwing the exception.");
                    Console.WriteLine("Press Enter when you're ready:");
                    Console.ReadLine();
                    Console.WriteLine("continue ...");
                }
            };

        // Open a general connection.
        s_generalConnection.Open();

        try
        {
            // Assume the database is creating and other services are going to connect to it.
            RetryCommand(provider);
        }
        catch
        {
            s_generalConnection.Close();
            // exception is thrown if connecting to the database isn't successful.
            throw;
        }
        s_generalConnection.Close();
    }

    private static void ExecuteCommand(SqlConnection cn, string command)
    {
        using var cmd = cn.CreateCommand();
        cmd.CommandText = command;
        cmd.ExecuteNonQuery();
    }

    private static void FindActiveSessions(SqlConnection cnn, string dbName)
    {
        using var cmd = cnn.CreateCommand();
        cmd.CommandText = "DECLARE @query NVARCHAR(max) = '';" + Environment.NewLine +
            $"SELECT @query = @query + 'KILL ' + CAST(spid as varchar(50)) + ';' FROM sys.sysprocesses WHERE dbid = DB_ID('{dbName}')" + Environment.NewLine +
            "SELECT @query AS Active_sessions;";
        var reader = cmd.ExecuteReader();
        if (reader.Read())
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.Write($">> Execute the '{reader.GetString(0)}' command in SQL Server to unblock the running task.");
            Console.ResetColor();
        }
        reader.Close();
    }

동기 명령과 함께 사용하는 방법

private static void RetryCommand(SqlRetryLogicBaseProvider provider)
{
    // Change this if you already have a database with the same name in your database.
    string dbName = "RetryCommand_TestDatabase";

    // Subscribe a new event on retry event and discover the active sessions on a database
    EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) =>
    {
        // Run just at first execution
        if (e.RetryCount == 1)
        {
            FindActiveSessions(s_generalConnection, dbName);
            Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts.");
        }
    };

    provider.Retrying += retryEvent;

    // Create a new database.
    ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName));
    Console.WriteLine($"The '{dbName}' database is created.");

    // Open a connection to the newly created database to block it from being dropped.
    using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName));
    blockingCnn.Open();
    Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped.");

    Console.WriteLine($"Dropping `{dbName}`...");
    // Try to drop the new database.
    RetryCommandSync(provider, dbName);

    Console.WriteLine("Command executed successfully.");

    provider.Retrying -= retryEvent;
}

private static void RetryCommandSync(SqlRetryLogicBaseProvider provider, string dbName)
{
    using var cmd = s_generalConnection.CreateCommand();
    cmd.CommandText = string.Format(DropDatabaseFormat, dbName);
    // 3. Assign the `provider` to the command
    cmd.RetryLogicProvider = provider;
    Console.WriteLine("The first attempt, before getting into the retry logic.");
    cmd.ExecuteNonQuery();
}

비동기 명령과 함께 사용하는 방법

private static void RetryCommand(SqlRetryLogicBaseProvider provider)
{
    // Change this if you already have a database with the same name in your database.
    string dbName = "RetryCommand_TestDatabase";

    // Subscribe to the retry event and discover active sessions in a database
    EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) =>
    {
        // Run just at first execution
        if (e.RetryCount == 1)
        {
            FindActiveSessions(s_generalConnection, dbName);
            Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts.");
        }
    };

    provider.Retrying += retryEvent;

    // Create a new database.
    ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName));
    Console.WriteLine($"The '{dbName}' database is created.");

    // Open a connection to the newly created database to block it from being dropped.
    using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName));
    blockingCnn.Open();
    Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped.");

    Console.WriteLine("Dropping the database...");
    // Try to drop the new database.
    RetryCommandAsync(provider, dbName).Wait();

    Console.WriteLine("Command executed successfully.");

    provider.Retrying -= retryEvent;
}

private static async Task RetryCommandAsync(SqlRetryLogicBaseProvider provider, string dbName)
{
    using var cmd = s_generalConnection.CreateCommand();
    cmd.CommandText = string.Format(DropDatabaseFormat, dbName);
    // 3. Assign the `provider` to the command
    cmd.RetryLogicProvider = provider;
    Console.WriteLine("The first attempt, before getting into the retry logic.");
    await cmd.ExecuteNonQueryAsync();
}

레거시 비동기 명령과 함께 사용하는 방법

명령에 공급자를 할당하고 명령을 실행하는 것 외에도 다음 SqlRetryLogicBaseProvider 메서드를 사용하여 직접 실행할 수 있습니다.

private static void RetryCommand(SqlRetryLogicBaseProvider provider)
{
    // Change this if you already have a database with the same name in your database.
    string dbName = "RetryCommand_TestDatabase";

    // Subscribe to the retry event and discover the active sessions in a database
    EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) =>
    {
        // Run just at first execution
        if (e.RetryCount == 1)
        {
            FindActiveSessions(s_generalConnection, dbName);
            Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts.");
        }
    };

    provider.Retrying += retryEvent;

    // Create a new database.
    ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName));
    Console.WriteLine($"The '{dbName}' database is created.");

    // Open a connection to the newly created database to block it from being dropped.
    using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName));
    blockingCnn.Open();
    Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped.");

    Console.WriteLine("Dropping the database...");
    // Try to drop the new database.
    RetryCommandBeginExecuteAsync(provider, dbName).Wait();

    Console.WriteLine("Command executed successfully.");

    provider.Retrying -= retryEvent;
}

private static async Task RetryCommandBeginExecuteAsync(SqlRetryLogicBaseProvider provider, string dbName)
{
    using var cmd = s_generalConnection.CreateCommand();
    cmd.CommandText = string.Format(DropDatabaseFormat, dbName);
    // Execute the BeginExecuteXXX and EndExecuteXXX functions by using Task.Factory.FromAsync().
    // Apply the retry logic by using the ExecuteAsync function of the configurable retry logic provider.
    Console.WriteLine("The first attempt, before getting into the retry logic.");
    await provider.ExecuteAsync(cmd, () => Task.Factory.FromAsync(cmd.BeginExecuteNonQuery(), cmd.EndExecuteNonQuery));
}

참고

APM(비동기 프로그래밍 모델)은 및 로 Begin 시작하는 메서드 쌍과 End라는 IAsyncResult인터페이스를 사용하는 레거시 패턴입니다. 새 애플리케이션에서는 이 패턴을 사용하지 않는 것이 좋습니다. 이러한 메서드는 이전 버전과의 호환성을 위한 것입니다.

적용 대상