添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 上一篇文章: 步骤 3:使用 ADO.NET 连接到 SQL 的概念证明
  • 本主题提供一个用于演示自定义重试逻辑的 C# 代码示例。 重试逻辑可提供可靠性。 重试逻辑旨在正常处理暂时错误或暂时性故障,在程序等待几秒并重试后,这种错误或故障往往会自行消失 。

    暂时性故障的原因包括:

  • 支持 Internet 的网络出现短暂故障。
  • 云系统在收到你发送的查询后,可能会立即对其资源进行负载均衡。
  • 用于连接本地 Microsoft SQL Server 的 ADO.NET 类也可以连接到 Azure SQL 数据库。 但是,ADO.NET 类本身无法提供生产环境中所需的稳定性和可靠性。 客户端程序可能会遇到暂时性故障,在此情况下,客户端程序应该能够自行从故障中静默正常恢复。

    步骤 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 数据库,因为它包括 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++)
                    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:

  • 打开一个控制台窗口,你已在其中编译了 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,然后重新编译。
  • 若要了解其他最佳做法和设计指导原则,请访问连接到 SQL 数据库:链接、最佳做法和设计指导原则