Jeden pojedynczy SqlException
(może) obejmuje wiele błędów programu SQL Server. Możesz przejść przez nie za pomocą Errors
własność. Każdy błąd to SqlError
:
foreach (SqlError error in exception.Errors)
Każdy SqlError
ma Class
właściwość, której możesz użyć, aby z grubsza określić, czy możesz spróbować ponownie, czy nie (a w przypadku, gdy spróbujesz ponownie, jeśli musisz również odtworzyć połączenie). Z MSDN:
Class
<10 dotyczy błędów w przekazanych informacjach, wtedy (prawdopodobnie) nie możesz spróbować ponownie, jeśli najpierw nie poprawisz danych wejściowych.Class
od 11 do 16 są "generowane przez użytkownika", więc prawdopodobnie znowu nie możesz nic zrobić, jeśli użytkownik najpierw nie poprawi swoich danych wejściowych. Należy pamiętać, że klasa 16 obejmuje wiele tymczasowych błędy, a klasa 13 jest przeznaczona na zakleszczenia (dzięki EvZ), więc możesz wykluczyć te klasy, jeśli zajmiesz się nimi jedna po drugiej.Class
od 17 do 24 to ogólne błędy sprzętu/oprogramowania i możesz spróbować ponownie. KiedyClass
ma 20 lat lub więcej, musisz odtworzyć połączenie też. 22 i 23 mogą być poważnymi błędami sprzętu/oprogramowania, 24 oznacza błąd nośnika (coś, co użytkownik powinien ostrzec, ale możesz spróbować ponownie, jeśli był to tylko „tymczasowy” błąd).
Bardziej szczegółowy opis każdej klasy znajdziesz tutaj.
Ogólnie rzecz biorąc, jeśli obsługujesz błędy z ich klasą, nie musisz znać dokładnie każdego błędu (używając error.Number
właściwość lub exception.Number
który jest tylko skrótem do pierwszego SqlError
na tej liście). Ma to tę wadę, że możesz spróbować ponownie, gdy nie jest to przydatne (lub nie można naprawić błędu). Proponuję podejście dwuetapowe :
- Sprawdź znane kody błędów (wymień kody błędów za pomocą
SELECT * FROM master.sys.messages
), aby zobaczyć, z czym chcesz sobie poradzić (wiedząc jak). Ten widok zawiera wiadomości we wszystkich obsługiwanych językach, więc może być konieczne przefiltrowanie ich wedługmsglangid
kolumna (na przykład 1033 dla angielskiego). - Wszystko inne polega na klasie błędu, ponawiaj próbę, gdy
Class
ma 13 lub więcej niż 16 (i ponowne łączenie, jeśli 20 lub więcej). - Błędy o wadze większej niż 21 (22, 23 i 24) są poważnymi błędami i krótkie oczekiwanie nie naprawi tych problemów (sama baza danych również może być uszkodzona).
Jedno słowo o wyższych klasach. Sposób radzenia sobie z tymi błędami nie jest prosty i zależy od wielu czynników (w tym zarządzania ryzykiem dla twojej aplikacji). Pierwszym prostym krokiem nie byłoby ponowne próbowanie o 22, 23 i 24 podczas próby operacji zapisu:jeśli baza danych, system plików lub nośnik są poważnie uszkodzone, zapisanie nowych danych może jeszcze bardziej pogorszyć integralność danych (SQL Server jest bardzo ostrożny nie narażaj DB dla zapytania nawet w krytycznych okolicznościach). Uszkodzony serwer, zależy to od architektury sieci DB, może nawet zostać poddany wymianie podczas pracy (automatycznie, po określonym czasie lub po uruchomieniu określonego wyzwalacza). Zawsze konsultuj się i pracuj blisko swojego DBA.
Strategia ponawiania próby zależy od błędu, który masz do czynienia:wolne zasoby, oczekiwanie na zakończenie oczekującej operacji, podjęcie alternatywnego działania itp. Ogólnie należy ponowić próbę tylko wtedy, gdy wszystkie błędy są możliwe do powtórzenia:
bool rebuildConnection = true; // First try connection must be open
for (int i=0; i < MaximumNumberOfRetries; ++i) {
try {
// (Re)Create connection to SQL Server
if (rebuildConnection) {
if (connection != null)
connection.Dispose();
// Create connection and open it...
}
// Perform your task
// No exceptions, task has been completed
break;
}
catch (SqlException e) {
if (e.Errors.Cast<SqlError>().All(x => CanRetry(x))) {
// What to do? Handle that here, also checking Number property.
// For Class < 20 you may simply Thread.Sleep(DelayOnError);
rebuildConnection = e.Errors
.Cast<SqlError>()
.Any(x => x.Class >= 20);
continue;
}
throw;
}
}
Zapakuj wszystko w try
/finally
aby prawidłowo zutylizować połączenie. Dzięki tej prostej-fałszywie naiwnej CanRetry()
funkcja:
private static readonly int[] RetriableClasses = { 13, 16, 17, 18, 19, 20, 21, 22, 24 };
private static bool CanRetry(SqlError error) {
// Use this switch if you want to handle only well-known errors,
// remove it if you want to always retry. A "blacklist" approach may
// also work: return false when you're sure you can't recover from one
// error and rely on Class for anything else.
switch (error.Number) {
// Handle well-known error codes,
}
// Handle unknown errors with severity 21 or less. 22 or more
// indicates a serious error that need to be manually fixed.
// 24 indicates media errors. They're serious errors (that should
// be also notified) but we may retry...
return RetriableClasses.Contains(error.Class); // LINQ...
}
Kilka dość skomplikowanych sposobów na znalezienie listy niekrytycznych błędów.
Zazwyczaj osadzam cały ten (boilerplate) kod w jednej metodzie (gdzie mogę ukryć wszystkie brudne rzeczy zrobić, aby utworzyć/zlikwidować/odtworzyć połączenie) z tą sygnaturą:
public static void Try(
Func<SqlConnection> connectionFactory,
Action<SqlCommand> performer);
Do użycia w ten sposób:
Try(
() => new SqlConnection(connectionString),
cmd => {
cmd.CommandText = "SELECT * FROM master.sys.messages";
using (var reader = cmd.ExecuteReader()) {
// Do stuff
}
});
Zwróć uwagęże szkielet (ponowna próba w przypadku błędu) może być używany także wtedy, gdy nie pracujesz z SQL Server (właściwie może być używany do wielu innych operacji, takich jak I/O i inne rzeczy związane z siecią, więc sugeruję napisanie ogólnej funkcji i wielokrotne używanie go).