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

屬性值

當預設為空時,會使用預設的不可重試提供者。

屬性

備註

你必須在執行指令前先設定該屬性的值,該屬性才會生效。

要套用重試邏輯,請在執行指令前先完成以下步驟:

  1. 請依照類型 SqlRetryLogicOption 定義配置參數。
  2. 請使用以下類別的SqlConfigurableRetryFactory靜態方法之一建立 :SqlRetryLogicBaseProvider
  1. 將物件指派 SqlRetryLogicBaseProvider 到屬性上 RetryLogicProvider

Note

偵測可重試例外是重試模式中非常重要的一環。 在套用重試邏輯之前,重要的是要調查例外情況,並選擇最適合你情況的重試服務提供者。 首先,記錄你的例外並找出暫時性故障。

Note

在重試邏輯中,每次執行指令後,指令逾 會重新開始,且在套用重試時間延遲後。 這兩個動作的時間點完全不重疊。

Note

除非在應用程式設定檔中設定,否則預設的重試邏輯提供者不會被啟用。 欲了解更多資訊,請參閱 可配置重試邏輯設定檔

注意事項

一個指令 CommandBehavior.CloseConnection 和內建的重試邏輯不相容。 底層連線在第一次執行嘗試後立即關閉,且不再可供後續重試使用。

Example

以下範例建立資料庫並建立與其的主動連線。 當資料庫有活躍連線時,它會嘗試丟棄該連線,使用 SqlConnection new 和 a SqlCommand ,而 a 則使用 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));
}

Note

非同步程式設計模型(APM)是一種傳統模式,使用一對以 BeginEnd開頭的方法,以及一個稱為 IAsyncResult的介面。 不建議在新應用中使用此圖案。 這些方法是為了向下相容。

適用於