步驟 4:使用 ADO.NET 彈性地連線到 SQL

下載 ADO.NET

本主題提供的 C# 程式碼範例示範自訂的重試邏輯。 重試邏輯會提供可靠性。 重試邏輯的設計目的在於妥善處理暫時錯誤或「暫時性錯誤」 ,此類錯誤通常會在程式等候數秒並重試之後消失。

暫時性錯誤的來源包括:

  • 支援網際網路的網路短暫失敗。
  • 傳送您的查詢時,雲端系統可能正在對其資源進行負載平衡。

用於連接到本機 Microsoft SQL Server 的 ADO.NET 類別也可以連接到 Azure SQL Database。 不過,ADO.NET 類別本身無法提供在生產環境使用所需的所有穩定性和可靠性。 用戶端程式可能會遇到暫時性錯誤,用戶端程式應該會從失敗中無訊息且正常自行復原並繼續。

步驟 1:識別暫時性錯誤

您的程式必須區分暫時性錯誤與持續性錯誤。 暫時性錯誤是可能在短時間內清除的錯誤狀況,例如暫時性網路問題。 假設您的程式拼錯了目標資料庫名稱,就是一個持續性錯誤的範例:在此情況下,「找不到這類資料庫」錯誤就會持續存在,而且沒有機會在短時間內清除。

您可以在 SQL Database 用戶端應用程式的錯誤訊息 \(部分機器翻譯\) 上取得已分類為暫時性錯誤的錯誤號碼清單。

步驟 2:建立並執行範例應用程式

此範例假設已安裝 .NET Framework 4.6.2 或更新版本。 C# 程式碼範例包含一個名為 Program.cs 的檔案。 下一節會提供它的程式碼。

步驟 2.a:擷取並編譯程式碼範例

您可以使用下列步驟編譯此範例:

  1. 免費的 Visual Studio Community 版本中,透過 C# 主控台應用程式範本建立新的專案。
    • [檔案] > [新增] > [專案] > [已安裝] > [範本] > [Visual C#] > [Windows] > [傳統桌面] > [主控台應用程式]
    • 將專案命名為 RetryAdo2
  2. 開啟 [方案總管] 窗格。
    • 查看專案的名稱。
    • 在您的專案中,在 Microsoft.Data.SqlClient 套件上新增 NuGet 相依性
    • 查看 Program.cs 檔案的名稱。
  3. 開啟 Program.cs 檔案。
  4. 使用下列程式碼區塊中的程式碼,取代 Program.cs 檔案的所有內容。
  5. 按一下 [建置] 功能表 > [建置解決方案]。

步驟 2.b:複製並貼上範例程式碼

將此程式碼到貼到您的 Program.cs 檔案中。

接著,您必須編輯伺服器名稱、密碼等項目的字串。 您可以在名為 GetSqlConnectionString 的方法中找到這些字串。

注意:伺服器名稱的連接字串適用於 Azure SQL Database,因其包含 tcp: 這四個字元前置詞。 但是,您可以調整伺服器字串,以連線到您的 Microsoft SQL Server。

using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Threading;

namespace RetryAdo2; 

public class Program
{
    public static int Main(string[] args)
    {
        bool succeeded = false;
        const int totalNumberOfTimesToTry = 4;
        int retryIntervalSeconds    = 10;

        for (int tries = 1; tries <= totalNumberOfTimesToTry; tries++)
        {
            try
            {
                if (tries > 1)
                {
                    Console.WriteLine(
                        "Transient error encountered. Will begin attempt number {0} of {1} max...",
                        tries,
                        totalNumberOfTimesToTry
                    );
                    Thread.Sleep(1000 * retryIntervalSeconds);
                    retryIntervalSeconds = Convert.ToInt32(retryIntervalSeconds * 1.5);
                }
                AccessDatabase();
                succeeded = true;
                break;
            }
            catch (SqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred.", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                succeeded = false;
                break;
            }
            catch (TestSqlException sqlExc) {
                if (TransientErrorNumbers.Contains(sqlExc.Number))
                {
                    Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
                    continue;
                }

                Console.WriteLine(sqlExc);
                succeeded = false;
                break;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                succeeded = false;
                break;
            }
        }

        if (!succeeded) {
            Console.WriteLine("ERROR: Unable to access the database!");
            return 1;
        }

        return 0;
    }

    /// <summary>
    /// Connects to the database, reads,
    /// prints results to the console.
    /// </summary>
    static void AccessDatabase() {
        //throw new TestSqlException(4060); //(7654321);  // Uncomment for testing.

        using var sqlConnection = new SqlConnection(GetSqlConnectionString());

        using var dbCommand = sqlConnection.CreateCommand();

        dbCommand.CommandText =
            @"  
SELECT TOP 3  
	ob.name,  
	CAST(ob.object_id as nvarchar(32)) as [object_id]  
  FROM sys.objects as ob  
  WHERE ob.type='IT'  
  ORDER BY ob.name;";

        sqlConnection.Open();
        var dataReader = dbCommand.ExecuteReader();

        while (dataReader.Read())
        {
            Console.WriteLine(
                "{0}\t{1}",
                dataReader.GetString(0),
                dataReader.GetString(1)
            );
        }
    }

    /// <summary>
    /// You must edit the four 'my' string values.
    /// </summary>
    /// <returns>An ADO.NET connection string.</returns>
    static private string GetSqlConnectionString()
    {
        // Prepare the connection string to Azure SQL Database.
        var sqlConnectionSB = new SqlConnectionStringBuilder 
        {
            // Change these values to your values.
            DataSource           = "tcp:myazuresqldbserver.database.windows.net,1433", //["Server"]
            InitialCatalog       = "MyDatabase",                                       //["Database"]
            UserID               = "MyLogin",                                          // "@yourservername"  as suffix sometimes.
            Password             = "MyPassword",
            // Adjust these values if you like. (ADO.NET 4.5.1 or later.)
            ConnectRetryCount    = 3,
            ConnectRetryInterval = 10, // Seconds.
            // Leave these values as they are.
            IntegratedSecurity = false,
            Encrypt            = true,
            ConnectTimeout     = 30
        };

        return sqlConnectionSB.ToString();
    }

    static List<int> TransientErrorNumbers = new() 
    {
        4060, 40197, 40501, 40613, 49918, 49919, 49920, 11001
    };
}

/// <summary>
/// For testing retry logic, you can have method
/// AccessDatabase start by throwing a new
/// TestSqlException with a Number that does
/// or does not match a transient error number
/// present in TransientErrorNumbers.
/// </summary>
internal class TestSqlException : ApplicationException
{
    internal TestSqlException(int testErrorNumber)
    {
        Number = testErrorNumber;
    }

    internal int Number { get; set; }
}

步驟 2.c:執行程式

RetryAdo2.exe 可執行檔不會輸入任何參數。 執行 .exe:

  1. 開啟主控台視窗至您編譯 RetryAdo2.exe 二進位檔的位置。
  2. 執行 RetryAdo2.exe,不含輸入參數。
database_firewall_rules_table   245575913  
filestream_tombstone_2073058421 2073058421  
filetable_updates_2105058535    2105058535  

步驟 3:測試重試邏輯的方式

有許多種方式可讓您模擬暫時性錯誤,以便測試您的重試邏輯。

步驟 3.a:擲回測試例外狀況

程式碼範例包含:

  • 第二個名為 TestSqlException 的小型類別,其中含有名為 Number 的屬性。
  • //throw new TestSqlException(4060); ,您可以將它取消註解。

如果您將 throw 陳述式取消註解,並重新編譯,那麼下次執行的 RetryAdo2.exe 輸出會類似下列內容。

[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]  
>> RetryAdo2.exe  
4060: transient occurred. (TESTING.)  
Transient error encountered. Will begin attempt number 2 of 4 max...  
4060: transient occurred. (TESTING.)  
Transient error encountered. Will begin attempt number 3 of 4 max...  
4060: transient occurred. (TESTING.)  
Transient error encountered. Will begin attempt number 4 of 4 max...  
4060: transient occurred. (TESTING.)  
ERROR: Unable to access the database!  
  
[C:\VS15\RetryAdo2\RetryAdo2\bin\Debug\]  
>>  

步驟 3.b:使用持續性錯誤重新測試

若要證明程式碼正確地處理持續性錯誤,請重新執行先前的測試,但不要使用類似 4060 的真正暫時性錯誤的數字。 請改為使用無意義的數字 7654321。 此程式應該將這視為持續性錯誤,並且應該略過任何重試。

步驟 3.c:中斷網路連線

  1. 中斷用戶端電腦的網路連線。
    • 若為桌上型電腦,請拔除網路線。
    • 若為膝上型電腦,請按功能鍵組合來關閉網路介面卡。
  2. 啟動 RetryAdo2.exe,並等候主控台顯示第一個暫時性錯誤,可能是 11001。
  3. 在 RetryAdo2.exe 繼續執行時,重新連接網路。
  4. 觀看主控台報告後續的重試成功。

步驟 3.d:暫時拼錯伺服器名稱

  1. 暫時將 40615 當作另一個錯誤號碼加入 TransientErrorNumbers中,並重新編譯。
  2. new QC.SqlConnectionStringBuilder()這行設定中斷點。
  3. 使用「編輯後繼續」 功能,在以下幾行中刻意拼錯伺服器名稱。
    • 讓程式執行,並返回您的中斷點。
    • 會發生錯誤 40615。
  4. 修正拼字錯誤。
  5. 讓程式執行並成功完成。
  6. 移除 40615,然後重新編譯。

後續步驟

若要探索其他最佳做法和設計指導方針,請造訪連線至 SQL Database:連結、最佳做法和設計指導方針 \(部分機器翻譯\)