本文提供一個 C# 程式碼範例,示範手動實作的重試邏輯。 重試邏輯處理暫時錯誤或 暫時錯誤,若程式等待並嘗試該操作,這些錯誤可能會被清除。
Important
對於使用 Microsoft.Data.SqlClient 3.0 及更新版本的新應用程式,建議優先使用驅動程式中可設定的重試邏輯,而不是自行實作重試迴圈。 先從 SqlClient 中的 Configure retry 邏輯開始。
暫時性錯誤的來源包括:
- 支援網際網路的短暫故障。
- 當你的查詢到來時,雲端系統可能正在進行負載平衡。
用於連線到 SQL Server 的 ADO.NET 類別也可以連線到 Azure SQL Database。 您的客戶端程式可能會遇到暫時性故障。 它應該會透過使用適合操作的有界重試策略來恢復。
步驟 1:識別暫時性錯誤
你的程式必須區分暫時錯誤與持續錯誤。 瞬態錯誤是指可能在短時間內消失的錯誤狀況,例如暫時性網路問題。 持續錯誤的例子是目標資料庫名稱的拼寫錯誤。 在這種情況下,「找不到此類資料庫」的錯誤會持續存在,且不會很快消失。
被歸類為暫時性錯誤的錯誤編號清單,請參閱 SQL 資料庫用戶端應用程式的錯誤訊息。
步驟 2:建立並執行範例應用程式
此範例假設已安裝 .NET Framework 4.6.2 或更新版本。 C# 程式碼範例包含一個名為 Program.cs的檔案。 下一節會提供它的程式碼。
步驟 2.a:擷取並編譯程式碼範例
您可以使用下列步驟編譯此範例:
在 免費的 Visual Studio Community 版本中,透過 C# 主控台應用程式範本建立新的專案。
選擇檔案>新增>專案>已安裝>範本>Visual C#>Windows>傳統桌面>主控台應用程式。
將專案命名為
RetryAdo2。
開啟 [方案總管] 窗格。
- 查看專案的名稱。
- 在您的專案中,在 Microsoft.Data.SqlClient 套件上新增 NuGet 相依性。
- 查看檔案名稱
Program.cs。
開啟
Program.cs檔案。將
Program.cs檔案的全部內容替換為下方程式碼區塊中的程式碼。選擇 「建置>建置解決方案」選單。
步驟 2.b:複製並貼上範例程式碼
把這段程式碼貼到你的 Program.cs 檔案裡。
接著,編輯伺服器名稱和密碼的字串。 你可以在名為 GetSqlConnectionString的方法中找到這些字串。
注意
這個 連接字串 是針對 Azure SQL Database 設計的,因為它包含了四個字元的前綴tcp:。 但你可以調整伺服器字串來連接到你的 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);
break;
}
catch (TestSqlException sqlExc) {
if (TransientErrorNumbers.Contains(sqlExc.Number))
{
Console.WriteLine("{0}: transient occurred. (TESTING.)", sqlExc.Number);
continue;
}
Console.WriteLine(sqlExc);
break;
}
catch (Exception e)
{
Console.WriteLine(e);
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>
/// Edit the four string values in accordance with your environment.
/// </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 = "<password>",
// 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:
- 在你編譯出
RetryAdo2.exe二進位檔的資料夾中開啟主控台視窗。 - 執行
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:中斷網路連線
- 中斷用戶端電腦的網路連線。
- 若為桌上型電腦,請拔除網路線。
- 若為膝上型電腦,請按功能鍵組合來關閉網路介面卡。
- 開始
RetryAdo2.exe,等待主控台顯示第一個暫態錯誤,可能是 11001。 - 重新連接網路,同時
RetryAdo2.exe繼續運行。 - 觀看主控台報告後續的重試成功。
步驟 3.d:暫時拼錯伺服器名稱
- 暫時將 40615 作為另一個錯誤編號加入至
TransientErrorNumbers中,然後重新編譯。 - 在
new QC.SqlConnectionStringBuilder()這行設定中斷點。 - 使用「編輯後繼續」 功能,在以下幾行中刻意拼錯伺服器名稱。
- 讓程式執行,並返回您的中斷點。
- 會發生錯誤 40615。
- 修正拼字錯誤。
- 讓程式執行並成功完成。
- 移除 40615,然後重新編譯。