Шаг 4. Выполнение устойчивого подключения к SQL с помощью ADO.NET
- Предыдущая статья: Шаг 3. Эксперимент, подразумевающий подключение к SQL с помощью ADO.NET
Этот раздел содержит пример кода C#, который демонстрирует логику повторных попыток. Логика повторных попыток обеспечивает надежность. Логика повторных попыток предназначена для корректной обработки временных ошибок или временных сбоев, которые обычно устраняются, если программа ожидает несколько секунд, а затем выполняет повторную попытку.
Ниже перечислены источники временных сбоев.
- Кратковременный сбой в работе сети, поддерживающей Интернет.
- Облачная система может балансировать нагрузку на свои ресурсы в момент отправки запроса.
Классы ADO.NET, которые используются для подключения к локальному серверу Microsoft SQL, также можно подключать к базе данных SQL Azure. При этом сами по себе классы ADO.NET необходимый для работы уровень надежности обеспечить не могут. Ваша клиентская программа может испытывать временные сбои, с которыми она должна справляться самостоятельно, продолжая работу без вмешательства пользователя.
Шаг 1. Определение временных ошибок
Программа должна отличать временные ошибки от постоянных. Временные ошибки — это условия возникновения ошибок, которые можно устранить в течение короткого периода времени, например, временные проблемы с сетью. Примером постоянной ошибки может быть случай, когда программа содержит опечатку в имени целевой базы данных. В этом случае ошибка "No such database found" (Такой базы данных не найдено) будет сохраняться, и не будет устранена в течение короткого времени.
Список номеров ошибок, которые считаются временными сбоями, можно найти в статье Troubleshooting connectivity issues and other errors with Microsoft Azure SQL Database (Устранение неполадок с соединениями и других ошибок с Базой данных SQL Microsoft Azure)
Шаг 2. Создание и запуск примера приложения
В том примере предполагается, что установлена среда .NET Framework версии 4.6.2 или более поздней версии. Пример кода C# состоит из одного файла с именем Program.cs. Этот код приведен в следующем разделе.
Шаг 2.a. Сбор и компиляция примера кода
Чтобы скомпилировать пример кода, выполните указанные ниже действия.
- В бесплатном выпуске Visual Studio Communityсоздайте новый проект на основе шаблона консольного приложения C#.
- Выберите Файл > Создать > Проект > Установленные> Шаблоны > Visual C# > Windows > Классический рабочий стол > Консольное приложение.
- Присвойте проекту имя RetryAdo2.
- Откройте панель обозревателя решений.
- Найдите имя своего проекта.
- В проекте добавьте NuGet-зависимость от пакета Microsoft.Data.SqlClient.
- Найдите имя файла Program.cs.
- Откройте файл Program.cs.
- Замените все содержимое файла Program.cs приведенным ниже кодом.
- Щелкните меню "Сборка > Собрать решение".
Шаг 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 = "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 не принимает никаких параметров. Чтобы запустить ЕХЕ-файл:
- откройте окно консоли — там, где была выполнена компиляция двоичного файла 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.г. Временная опечатка в имени сервера
- Временно добавьте 40615 в поле TransientErrorNumbersв качестве другого номера ошибки, а затем выполните повторную компиляцию.
- Задайте точку останова строке:
new QC.SqlConnectionStringBuilder()
. - Используйте параметр Изменить и продолжить, чтобы специально исказить имя сервера несколькими строками ниже.
- Запустите программу и вернитесь к точке останова.
- Произойдет ошибка 40615.
- Исправьте ошибку в имени.
- Запустите программу и дождитесь успешного завершения процедуры.
- Удалите 40615 и выполните повторную компиляцию.
Следующие шаги
Чтобы изучить другие лучшие практики и рекомендации по проектированию, посетите страницу "Подключение к База данных SQL: ссылки, рекомендации и рекомендации по проектированию"