Едно единствено SqlException
(може) обвива множество грешки в SQL Server. Можете да ги преглеждате с Errors
Имот. Всяка грешка е SqlError
:
foreach (SqlError error in exception.Errors)
Всяка SqlError
има Class
свойство, което можете да използвате, за да определите грубо дали можете да опитате отново или не (и в случай, че опитате отново, ако трябва да пресъздадете и връзката). От MSDN:
Class
<10 е за грешки в информацията, която сте предали, след което (вероятно) не можете да опитате отново, ако първо не коригирате въведените данни.Class
от 11 до 16 са "генерирани от потребител", тогава вероятно отново не можете да направите нищо, ако потребителят първо не коригира своите входове. Моля, имайте предвид, че клас 16 включва много временни грешки и клас 13 е за блокиране (благодарение на EvZ), така че можете да изключите тези класове, ако ги обработвате един по един.Class
от 17 до 24 са общи хардуерни/софтуерни грешки и можете да опитате отново. КогатоClass
е 20 или повече, трябва да пресъздадете връзката също. 22 и 23 може да са сериозни хардуерни/софтуерни грешки, 24 показва грешка в носителя (нещо потребителят трябва да бъде предупреден, но можете да опитате отново, в случай че е просто „временна“ грешка).
Можете да намерите по-подробно описание на всеки клас тук.
Като цяло, ако обработвате грешки с техния клас, няма да е необходимо да знаете точно всяка грешка (използвайки error.Number
свойство или exception.Number
което е просто пряк път за първата SqlError
в този списък). Това има недостатъка, че можете да опитате отново, когато не е полезно (или грешката не може да бъде възстановена). Бих предложил подход в две стъпки :
- Проверете за известни кодове за грешки (избройте кодовете за грешки с
SELECT * FROM master.sys.messages
), за да видите с какво искате да се справите (знаете как). Този изглед съдържа съобщения на всички поддържани езици, така че може да се наложи да ги филтрирате поmsglangid
колона (например 1033 за английски). - За всичко останало разчитайте на клас за грешка, като опитайте отново, когато
Class
е 13 или по-високо от 16 (и повторно свързване, ако е 20 или по-високо). - Грешките с сериозност по-висока от 21 (22, 23 и 24) са сериозни грешки и малкото чакане няма да реши тези проблеми (самата база данни също може да бъде повредена).
Една дума за по-високите класове. Как да се справите с тези грешки не е лесно и зависи от много фактори (включително управление на риска за вашето приложение). Като проста първа стъпка не бих опитал отново за 22, 23 и 24 при опит за операция за запис:ако базата данни, файловата система или носителят са сериозно повредени, тогава записването на нови данни може да влоши целостта на данните още повече (SQL Server е изключително внимателен за не компрометирайте DB за заявка дори при критични обстоятелства). Повреден сървър, зависи от вашата мрежова архитектура на DB, може дори да бъде сменен горещо (автоматично, след определен период от време или когато се задейства определен тригер). Винаги се консултирайте и работете близо до вашия DBA.
Стратегията за повторен опит зависи от грешката, която обработвате:безплатни ресурси, изчакайте завършване на чакаща операция, предприемете алтернативно действие и т.н. По принцип трябва да опитате отново само ако всички грешките са "възможни за повторен опит":
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;
}
}
Увийте всичко в try
/finally
за правилно изхвърляне на връзката. С този просто-фалшиво-наивен CanRetry()
функция:
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...
}
Някои доста трудни начини да намерите списък с некритични грешки тук.
Обикновено вграждам целия този (шаблон) код в един метод (където мога да скрия всички мръсни неща направено за създаване/изхвърляне/пресъздаване на връзка) с този подпис:
public static void Try(
Func<SqlConnection> connectionFactory,
Action<SqlCommand> performer);
Да се използва по следния начин:
Try(
() => new SqlConnection(connectionString),
cmd => {
cmd.CommandText = "SELECT * FROM master.sys.messages";
using (var reader = cmd.ExecuteReader()) {
// Do stuff
}
});
Моля, имайте предвид, че скелетът (повторен опит при грешка) може да се използва и когато не работите със SQL Server (всъщност може да се използва за много други операции като I/O и неща, свързани с мрежата, така че бих предложил да напишете обща функция и да го използвате многократно).