Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Microsoft.Data.SqlClient zgłasza awarie serwera i dostawcy przez SqlException. Operacja bazodanowa może zwracać wiele błędów, więc przeglądaj kolekcję Errors wyjątku zamiast polegać wyłącznie na połączonym Message.
Ostrzeżenia i komunikaty informacyjne z silnika bazy danych są dostarczane przez InfoMessage. Zasubskrybuj przed otwarciem połączenia lub wykonaniem polecenia, które może wygenerować wiadomość.
Obsługuj awarię bazy danych w następującej kolejności:
- Przechwyć
SqlExceptionw punkcie granicznym, w którym można dodać kontekst operacji lub wybrać działanie naprawcze. - Sprawdź wszystkie
SqlErrorw kolekcjiErrors. - Rejestruj ustrukturyzowane pola diagnostyczne bez rejestracji danych uwierzytelniających lub wrażliwych wartości.
- Sprawdź stan połączenia po awarii związanej z połączeniem.
- Spróbuj ponownie tylko wtedy, gdy błąd jest przejściowy i operacja jest bezpieczna do powtórzenia.
Przykłady w tym artykule zakładają, że otwarte połączenie SqlConnection o nazwie connection jest skierowane do przykładowej bazy danych AdventureWorks. Przykłady w języku C# wymagają using Microsoft.Data.SqlClient;.
Otrzymuj wiadomości informacyjne
Zdarzenie otrzymuje InfoMessage komunikaty serwera o niskim poziomie ważności, w tym wyjście z PRINT. Jedno zdarzenie może zawierać wiele SqlError obiektów. Zasubskrybuj przed wykonaniem polecenia, które może wygenerować wiadomość:
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();
Fragment wyświetla:
Info 0, class 0: Connected and ready.
Catch SqlException
Przechwyć SqlException wokół operacji na bazie danych, które możesz obsłużyć lub wzbogacić. Nie używaj ogólnego bloku catch, by zamienić nieudaną operację bazy danych w pomyślny wynik.
Operacja bazodanowa może zwrócić więcej niż jeden błąd. Każde SqlError w Errors zawiera pola strukturalne:
| Property | Meaning |
|---|---|
Number |
Numer błędu silnika bazy danych lub dostawcy. |
Class |
Poziom ważności. |
State |
Stan określony przez serwer, który potrafi wyróżnić lokalizacje dla tego samego numeru błędu. |
LineNumber |
Zgłoszony numer linii. |
Procedure |
Procedura lub nazwa wyzwalacza, gdy zostanie podana. |
Server |
Nazwa serwera zgłoszona dla błędu. |
Message |
Tekst błędu czytelny dla człowieka. |
Właściwości pomocnicze w elemencie SqlException, w tym Number, Class, State, LineNumber, Procedure i Server, odzwierciedlają tylko pierwszy błąd. Poniższy fragment drukuje wiadomość, a następnie powoduje błąd w tej samej partii. Odczytuje exception.Number i iteruje Errors , aby zgłosić każdy wpis:
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}");
}
}
}
Fragment wyświetla:
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.
Wyjście PRINT nie wywołuje InfoMessage. Gdy partia zwraca błąd, który powoduje SqlException, dostawca dodaje komunikaty informacyjne tej partii na końcu SqlException.Errors.
Ten fragment przechwytuje błąd kontrolowany tylko po to, aby pokazać jego pola. W kodzie aplikacji należy ponownie zgłosić wyjątek za pomocą throw;, chyba że blok catch podejmuje rzeczywiste działanie naprawcze. Podejmuj decyzje na podstawie udokumentowanych numerów błędów i kontekstu operacji. Nie analizuj zlokalizowanego tekstu komunikatu.
Sprawdzanie stanu połączenia
Silnik bazy danych przypisuje każdej wiadomości klasę ważności. Klasy pochodne mają charakter informacyjny. Klasy traktowane jako błędy dają SqlException. Błędy o wysokim stopniu ważności mogą pozostawić połączenie zamknięte lub zerwane.
Po błędzie sprawdź State przed ponownym użyciem połączenia:
using var command = new SqlCommand(
"RAISERROR (N'Controlled failure.', 16, 1);",
connection);
try
{
command.ExecuteNonQuery();
}
catch (SqlException)
{
Console.WriteLine($"Connection state: {connection.State}");
}
Nie zakładaj, że połączenie nadal działa, bo wyjątek został wychwycony. Błąd o poziomie 16 zwykle pozostawia go otwartym, ale awarie połączenia mogą go zamknąć. Usuń zamknięte lub zerwane połączenie i pobierz nowe połączenie z puli. Nie koduj polityki ponawiania prób tylko na podstawie poziomu ważności. Operacja musi być również bezpieczna do powtórzenia.
Zidentyfikuj nieudane polecenie wsadowe
Gdy SqlBatch zawiedzie, SqlException.BatchCommand może odwoływać się do powiązanego SqlBatchCommand. Właściwość ma wartość null, gdy dostawca nie może przypisać awarii jednemu poleceniu wsadowym. Poniższy fragment wykonuje dwa polecenia wsadowe. Druga kończy się niepowodzeniem:
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}");
}
}
}
Fragment drukuje:
Error 8134: Divide by zero error encountered.
Failed command: SELECT 1 / 0;
Tekst poleceń może zawierać literały wrażliwe w słabo parametryzowanych aplikacjach. Używaj stabilnych nazw operacji aplikacji w logach produkcyjnych i nie loguj domyślnie wartości parametrów.
Przekieruj błędy użytkownika do InfoMessage
Właściwość FireInfoMessageEventOnUserErrors domyślnie przyjmuje wartość false. Gdy ustawisz to na true, błędy o poważeniu 16 lub niższym trafiają do programu obsługi InfoMessage zamiast zgłaszania 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();
Nie jest zgłaszany żaden wyjątek. Fragment brzmi:
Info 50000, class 16: Routed to InfoMessage.
Używaj tej opcji tylko wtedy, gdy aplikacja ma celowy model błędu oparty na zdarzeniach, ponieważ w kodzie wywołującym wyjątek nie jest już widoczny. Błędy o ważności 17 lub wyższej nadal się pojawiają SqlException. Ustaw tę właściwość raz jako część konfiguracji połączenia, zamiast przełączać ją przy poszczególnych poleceniach.
Diagnostyczny kontekst dziennika
Uchwyć wystarczająco ustrukturyzowany kontekst, aby powiązać awarię bez ujawniania danych. Poniższy fragment rejestruje nazwę operacji aplikacji, ClientConnectionId oraz pola każdego SqlError.
ClientConnectionId Pomaga powiązać awarię klienta z diagnostyką serwera i sieci. To nie są dane uwierzytelniające.
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}");
}
}
}
Wartość ClientConnectionId różni się dla każdego połączenia. Fragment wyświetla wynik podobny do:
Operation=LoadOrders; ClientConnectionId=<connection ID>
Number=8134; Class=16; State=1; Procedure=; Line=1
Zapisz także, czy transakcja była aktywna i, jeśli dostępne są dane z BatchCommand, które polecenie wsadowe zawiodło. Uwzględniaj oczyszczone identyfikatory serwerów i baz danych tylko wtedy, gdy pozwala na to polityka organizacji. Nie loguj łańcuchów połączeń, tokenów dostępu, haseł ani wrażliwych parametrów. Korzystaj z liczników diagnostycznych, ze śledzenia źródeł zdarzeń oraz z przewodnika rozwiązywania problemów SqlClient do dokładniejszej analizy.
Treści powiązane
- Powrót do procesu wykonywania poleceń.
- Aby zidentyfikować nieudane polecenie w grupie, wykonaj wiele poleceń za pomocą SqlBatch.
- Aby dodać ograniczoną liczbę ponowień, użyj konfigurowalnej logiki ponawiania.
- Gdy pola wyjątków nie wystarczą, skorzystaj z poradnika SqlClient do rozwiązywania problemów.