Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Microsoft. Data.SqlClient сообщает о сбоях сервера и провайдера через SqlException. Операция базы данных может возвращать несколько ошибок, поэтому проверяйте коллекцию Errors исключения, а не полагайтесь только на объединённый Message.
Предупреждения и информационные сообщения от движка базы данных доставляются через InfoMessage. Подпишитесь до открытия соединения или выполнения команды, которая может создать сообщение.
Действия при сбое базы данных в следующем порядке:
- Перехватите
SqlExceptionна границе, которая может добавить контекст операции, или выбрать действие восстановления. - Проверьте все
SqlErrorв коллекцииErrors. - Записывайте структурированные диагностические поля без записи учетных данных или чувствительных значений.
- Проверьте состояние соединения после сбоя, связанного с подключением.
- Повторяйте только тогда, когда ошибка временная и операция безопасна для повторения.
Примеры в этой статье предполагают, что открытое соединение SqlConnection с именем connection нацелено на образцовую базу данных AdventureWorks. Примеры C# требуют using Microsoft.Data.SqlClient;.
Получайте информационные сообщения
Событие InfoMessage принимает сообщения сервера низкой степени, включая вывод из PRINT. Одно событие может содержать несколько SqlError объектов. Оформите подписку перед выполнением команды, которая может создать сообщение:
connection.InfoMessage += (_, eventArgs) =>
{
foreach (SqlError message in eventArgs.Errors)
{
Console.WriteLine(
$"Info {message.Number}, class {message.Class}: {message.Message}");
}
};
using var command = new SqlCommand(
"PRINT N'Connected and ready.';",
connection);
command.ExecuteNonQuery();
Выводится:
Info 0, class 0: Connected and ready.
Catch SqlException
Перехватывайте SqlException исключения при операции с базой данных, которые вы можете обработать или дополнить контекстом. Не используйте слишком общий обработчик исключений, чтобы превратить неудачную операцию базы данных в успешный результат.
Операция с базой данных может вернуть несколько ошибок. Каждый SqlError в Errors включает структурированные поля:
| Property | Meaning |
|---|---|
Number |
Номер ошибки ядра базы данных или поставщика данных. |
Class |
Степень серьезности. |
State |
Состояние, определённое сервером, которое может различать местоположения для одного и того же номера ошибки. |
LineNumber |
Указанный номер строки. |
Procedure |
Процедура или название триггера, если указано. |
Server |
Имя сервера, указанное для ошибки. |
Message |
Понятный человеку текст ошибки. |
Вспомогательные свойства на SqlException, включая Number, Class, State, LineNumber, Procedure, и Server, отражают только первую ошибку. Следующий отрывок печатает сообщение и затем вызывает ошибку в том же пакете. Он читает exception.Number, а затем перебирает Errors, чтобы вывести каждую запись:
using (var failing = new SqlCommand("""
PRINT N'Starting controlled operation.';
RAISERROR (N'The controlled operation failed.', 16, 1);
""", connection))
{
try
{
await failing.ExecuteNonQueryAsync();
}
catch (SqlException exception)
{
Console.WriteLine($"First error number: {exception.Number}");
foreach (SqlError error in exception.Errors)
{
Console.WriteLine(
$"Number={error.Number}; Class={error.Class}; " +
$"State={error.State}; Message={error.Message}");
}
}
}
Выводится фрагмент:
First error number: 50000
Number=50000; Class=16; State=1; Message=The controlled operation failed.
Number=0; Class=0; State=1; Message=Starting controlled operation.
Выход PRINT не вызывает InfoMessage. Когда пакет возвращает ошибку, которая вызывает SqlException, провайдер добавляет информационные сообщения этой партии в конец SqlException.Errors.
Этот отрывок фиксирует контролируемую ошибку только для того, чтобы показать её поля. В коде приложения повторно выбросьте исключение с помощью throw;, если блок catch не выполняет реальное восстановление. Принимайте решения, исходя из задокументированных номеров ошибок и операционного контекста. Не разбирайте локализованный текст сообщения.
Проверьте состояние подключения
Движок базы данных назначает каждому сообщению класс серьёзности. Нижележащие классы — информационные. Классы, рассматриваемые как ошибки, приводят к SqlException. Ошибки высокой серьезности могут оставить соединение закрытым или разорванным.
После ошибки проверьте State перед повторным использованием соединения:
using var command = new SqlCommand(
"RAISERROR (N'Controlled failure.', 16, 1);",
connection);
try
{
command.ExecuteNonQuery();
}
catch (SqlException)
{
Console.WriteLine($"Connection state: {connection.State}");
}
Не думайте, что соединение остаётся пригодным только потому, что исключение было перехвачено. Ошибка уровня серьезности 16 обычно оставляет его открытым, но при разрывах соединения он может закрыться. Удалите закрытое или сломанное соединение и возьмите другое соединение из пула. Не кодируйте политику повторных попыток только на основе уровня серьёзности. Операция также должна быть безопасной для повторения.
Идентифицировать пакетную команду, завершившуюся с ошибкой
Когда SqlBatch не выполняется, SqlException.BatchCommand может ссылаться на соответствующее SqlBatchCommand. Свойство имеет значение null, когда провайдер не может связать сбой с одной командой batch. Следующий отрывок выполняет две пакетные команды. Второе не удаётся:
using (var batch = new SqlBatch(connection))
{
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1;"));
batch.BatchCommands.Add(new SqlBatchCommand("SELECT 1 / 0;"));
try
{
await batch.ExecuteNonQueryAsync();
}
catch (SqlException exception)
{
Console.WriteLine($"Error {exception.Number}: {exception.Message}");
if (exception.BatchCommand is SqlBatchCommand failedCommand)
{
Console.WriteLine($"Failed command: {failedCommand.CommandText}");
}
}
}
Вывод отрывка:
Error 8134: Divide by zero error encountered.
Failed command: SELECT 1 / 0;
Текст команд может содержать чувствительные литералы в плохо параметризованных приложениях. Предпочитайте стабильные имена операций приложений в производственных логах и не записывайте значения параметров по умолчанию.
Направляйте ошибки пользователя в InfoMessage
FireInfoMessageEventOnUserErrors по умолчанию использует false. Когда вы устанавливаете это значение в true, ошибки с уровнем серьезности 16 или ниже передаются обработчику InfoMessage вместо вызова SqlException:
connection.InfoMessage += (_, eventArgs) =>
{
foreach (SqlError message in eventArgs.Errors)
{
Console.WriteLine(
$"Info {message.Number}, class {message.Class}: {message.Message}");
}
};
connection.FireInfoMessageEventOnUserErrors = true;
using var command = new SqlCommand(
"RAISERROR (N'Routed to InfoMessage.', 16, 1);",
connection);
command.ExecuteNonQuery();
Исключение не возникает. Вывод фрагмента:
Info 50000, class 16: Routed to InfoMessage.
Используйте этот вариант только если приложение намеренно использует модель ошибки на основе событий, поскольку вызывающий код больше не видит исключения. Ошибки с серьёзностью 17 и выше по-прежнему вызывают SqlException. Задайте свойство один раз в конфигурации соединения, а не переключайте его для каждой команды.
Контекст диагностики журнала
Зафиксировать достаточно структурированного контекста, чтобы сопоставить с ним сбой, не раскрывая данные. Следующий отрывок регистрирует имя операции приложения, ClientConnectionId, и поля каждого SqlError.
ClientConnectionId помогает связать сбой клиента с диагностикой серверов и сети. Это не учетные данные.
const string operation = "LoadOrders";
using (var load = new SqlCommand("SELECT 1 / 0;", connection))
{
try
{
await load.ExecuteScalarAsync();
}
catch (SqlException exception)
{
Console.WriteLine(
$"Operation={operation}; " +
$"ClientConnectionId={exception.ClientConnectionId}");
foreach (SqlError error in exception.Errors)
{
Console.WriteLine(
$"Number={error.Number}; Class={error.Class}; " +
$"State={error.State}; Procedure={error.Procedure}; " +
$"Line={error.LineNumber}");
}
}
}
Значение ClientConnectionId отличается при каждом соединении. Отрывок печатает результат, похожий на следующий:
Operation=LoadOrders; ClientConnectionId=<connection ID>
Number=8134; Class=16; State=1; Procedure=; Line=1
Также запишите, была ли транзакция активна и, если доступен BatchCommand, какая команда batch не сработала. Включайте очищенные идентификаторы серверов и баз данных только в том случае, если это позволяет организационная политика. Не фиксируйте строки соединения, токены доступа, пароли или чувствительные значения параметров. Используйте диагностические счётчики, отслеживание источников событий и руководство по устранению неполадок SqlClient для более глубокого изучения.
Связанные материалы
- Вернуться к рабочему процессу выполнения команд.
- Чтобы выявить неудачную команду в группе, выполните несколько команд с помощью SqlBatch.
- Для добавления ограниченных повторных попыток используйте настраиваемую логику повторных попыток.
- Если полей исключений недостаточно, используйте руководство по устранению неполадок SqlClient.