Поделиться через


Шаг 4. Выполнение устойчивого подключения к SQL с помощью ADO.NET

Скачать ADO.NET

Этот раздел содержит пример кода C#, который демонстрирует логику повторных попыток. Логика повторных попыток обеспечивает надежность. Логика повторных попыток предназначена для корректной обработки временных ошибок или временных сбоев, которые обычно устраняются, если программа ожидает несколько секунд, а затем выполняет повторную попытку.

Ниже перечислены источники временных сбоев.

  • Кратковременный сбой в работе сети, поддерживающей Интернет.
  • Облачная система может балансировать нагрузку на свои ресурсы в момент отправки запроса.

Классы ADO.NET, которые используются для подключения к локальному серверу Microsoft SQL, также можно подключать к базе данных SQL Azure. При этом сами по себе классы ADO.NET необходимый для работы уровень надежности обеспечить не могут. Ваша клиентская программа может испытывать временные сбои, с которыми она должна справляться самостоятельно, продолжая работу без вмешательства пользователя.

Шаг 1. Определение временных ошибок

Программа должна отличать временные ошибки от постоянных. Временные ошибки — это условия возникновения ошибок, которые можно устранить в течение короткого периода времени, например, временные проблемы с сетью. Примером постоянной ошибки может быть случай, когда программа содержит опечатку в имени целевой базы данных. В этом случае ошибка "No such database found" (Такой базы данных не найдено) будет сохраняться, и не будет устранена в течение короткого времени.

Список номеров ошибок, которые считаются временными сбоями, доступен в статье Error messages for SQL Database client applications

Шаг 2. Создание и запуск примера приложения

В том примере предполагается, что установлена среда .NET Framework версии 4.6.2 или более поздней версии. Пример кода C# состоит из одного файла с именем Program.cs. Этот код приведен в следующем разделе.

Шаг 2.a. Сбор и компиляция примера кода

Чтобы скомпилировать пример кода, выполните указанные ниже действия.

  1. В бесплатном выпуске Visual Studio Communityсоздайте новый проект на основе шаблона консольного приложения C#.
    • Файл > Новый > Проект > Установленные > Шаблоны > Visual C# > Windows > Классический рабочий стол > Консольное приложение.
    • Присвойте проекту имя RetryAdo2.
  2. Откройте панель обозревателя решений.
  3. Откройте файл Program.cs.
  4. Замените все содержимое файла Program.cs приведенным ниже кодом.
  5. Щелкните меню "Сборка > Собрать решение".

Шаг 2.b. Копирование и вставка примера кода

Вставьте этот код в ваш файл Program.cs .

Затем необходимо изменить строки для имени сервера, пароля и т. д. Эти строки можно найти в методе с именем GetSqlConnectionString.

Заметка

Строка подключения для имени сервера ориентирована на базу данных SQL Azure, так как она включает четырехсимвольный префикс 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             = "<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-файл:

  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.г. временно искажените имя сервера

  1. Временно добавьте 40615 в поле TransientErrorNumbersв качестве другого номера ошибки, а затем выполните повторную компиляцию.
  2. Задайте точку останова строке: new QC.SqlConnectionStringBuilder().
  3. Используйте функцию Изменить и продолжить, чтобы целенаправленно исказить имя сервера в нескольких строках ниже.
    • Запустите программу и вернитесь к точке останова.
    • Произойдет ошибка 40615.
  4. Исправьте орфографическую ошибку.
  5. Запустите программу и дождитесь успешного завершения процедуры.
  6. Удалите 40615 и выполните повторную компиляцию.

Следующие шаги

Чтобы изучить другие рекомендации и рекомендации по проектированию, посетите Подключение к базе данных SQL: ссылки, рекомендации и рекомендации по проектированию