Une seule SqlException
(peut) encapsule plusieurs erreurs SQL Server. Vous pouvez les parcourir avec Errors
biens. Chaque erreur est SqlError
:
foreach (SqlError error in exception.Errors)
Chaque SqlError
a une Class
propriété que vous pouvez utiliser pour déterminer approximativement si vous pouvez réessayer ou non (et si vous réessayez si vous devez également recréer la connexion). À partir de MSDN :
Class
<10 correspond à des erreurs dans les informations que vous avez transmises, alors (probablement) vous ne pouvez pas réessayer si vous ne corrigez pas les entrées au préalable.Class
de 11 à 16 sont "générés par l'utilisateur", alors vous ne pouvez probablement rien faire si l'utilisateur ne corrige pas ses entrées. Veuillez noter que la classe 16 comprend de nombreux temporaires erreurs et la classe 13 est pour les interblocages (grâce à EvZ) donc vous pouvez exclure ces classes si vous les gérez une par une.Class
de 17 à 24 sont des erreurs matérielles/logicielles génériques et vous pouvez réessayer. QuandClass
est de 20 ou plus, vous devez recréer la connexion aussi. 22 et 23 peuvent être des erreurs matérielles/logicielles graves, 24 indique une erreur de support (quelque chose que l'utilisateur doit être averti, mais vous pouvez réessayer au cas où il ne s'agirait que d'une erreur "temporaire").
Vous pouvez trouver une description plus détaillée de chaque classe ici.
En général, si vous gérez les erreurs avec leur classe, vous n'aurez pas besoin de connaître exactement chaque erreur (en utilisant error.Number
propriété ou exception.Number
qui est juste un raccourci pour le premier SqlError
dans cette liste). Cela a l'inconvénient que vous pouvez réessayer lorsque cela n'est pas utile (ou que l'erreur ne peut pas être récupérée). Je suggérerais une approche en deux étapes :
- Vérifiez les codes d'erreur connus (listez les codes d'erreur avec
SELECT * FROM master.sys.messages
) pour voir ce que vous voulez gérer (savoir comment). Cette vue contient des messages dans toutes les langues prises en charge, vous devrez donc peut-être les filtrer parmsglangid
colonne (par exemple 1033 pour l'anglais). - Pour tout le reste, s'appuyer sur la classe d'erreur, en réessayant lorsque
Class
est de 13 ou plus que 16 (et se reconnecte si 20 ou plus). - Les erreurs dont la gravité est supérieure à 21 (22, 23 et 24) sont des erreurs graves et peu d'attente ne résoudra pas ces problèmes (la base de données elle-même peut également être endommagée).
Un mot sur les classes supérieures. La gestion de ces erreurs n'est pas simple et dépend de nombreux facteurs (y compris la gestion des risques pour votre candidature). Comme première étape simple, je ne réessaierais pas pour 22, 23 et 24 lors d'une tentative d'écriture :si la base de données, le système de fichiers ou le support sont gravement endommagés, l'écriture de nouvelles données peut détériorer encore plus l'intégrité des données (SQL Server fait extrêmement attention à ne compromettez pas la base de données pour une requête, même dans des circonstances critiques). Un serveur endommagé, cela dépend de l'architecture de votre réseau de base de données, peut même être remplacé à chaud (automatiquement, après un laps de temps spécifié ou lorsqu'un déclencheur spécifié est déclenché). Consultez et travaillez toujours avec votre administrateur de bases de données.
La stratégie de nouvelle tentative dépend de l'erreur que vous gérez :libérer des ressources, attendre la fin d'une opération en attente, effectuer une action alternative, etc. En général, vous ne devez réessayer que si tous les erreurs sont "retry-able":
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;
}
}
Enveloppez tout dans try
/finally
pour disposer correctement de la connexion. Avec ce simple-faux-naïf CanRetry()
fonction :
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...
}
Quelques façons assez délicates de trouver la liste des erreurs non critiques ici.
Habituellement, j'intègre tout ce code (standard) dans une seule méthode (où je peux cacher toutes les choses sales done to create/dispose/recreate connection) avec cette signature :
public static void Try(
Func<SqlConnection> connectionFactory,
Action<SqlCommand> performer);
A utiliser comme ceci :
Try(
() => new SqlConnection(connectionString),
cmd => {
cmd.CommandText = "SELECT * FROM master.sys.messages";
using (var reader = cmd.ExecuteReader()) {
// Do stuff
}
});
Veuillez noter que le squelette (réessayer en cas d'erreur) peut également être utilisé lorsque vous ne travaillez pas avec SQL Server (en fait, il peut être utilisé pour de nombreuses autres opérations telles que les E/S et les éléments liés au réseau, je suggérerais donc d'écrire une fonction générale et de le réutiliser largement).