Exceptions

Toutes les exceptions du provider dérivent de NTiException, elle-même dérivée de DbException comme l'exige le contrat ADO.NET : un catch (DbException) générique attrape donc tout NTi (référence DbException).

DbException
  NTiException                  base de toutes les exceptions NTi
    NTiSqlException             erreur SQL du serveur database (SQLCODE négatif)
    NTiCommandException         échec d'une commande CL ou d'un appel de programme
    NTiCommunicationException   panne de communication, connexion cassée

Quelle condition lève quoi

Exception Levée quand Propriétés spécifiques
NTiException erreur du provider hors des trois catégories dérivées : mauvaise utilisation de l'API, conversion impossible (par exemple demander du texte en CCSID 65535), échec d'un rappel MFA (cause d'origine préservée en InnerException) celles de DbException
NTiSqlException le serveur database rend un SQLCODE négatif SqlCode ; SqlState (SQLSTATE de 5 caractères) ; SecondLevelMessage (texte de second niveau quand demandé)
NTiCommandException une commande CL, un appel de programme ou de programme de service échoue ReturnCode (code retour du serveur commande) ; Messages (pile de messages IBM i, voir ci-dessous)
NTiCommunicationException échec de connexion, timeout, fin de flux inattendue, trame malformée, erreur TLS IsTransient, toujours vrai : la connexion fautive est cassée et jetée, une nouvelle tentative sur une connexion fraîche peut réussir (stratégies de retry EF/Polly)

Sur net6 et plus, SqlState redéfinit DbException.SqlState : le code générique catch (DbException e) when (e.SqlState == "42704") fonctionne tel quel.

NTiCommandResult : la pile de messages

NTiCommandException.Messages porte la pile de messages IBM i (CPFxxxx, MCHxxxx...) rendue par le serveur commande ; chaque entrée est un NTiCommandResult :

Propriété Contenu
Id identifiant du message (CPF2105, MCH0602, SQL0204...)
Type code du type de message
Severity sévérité, 0 à 99
File fichier de messages
Library bibliothèque du fichier de messages
Text texte de premier niveau
SubstitutionData données de substitution du message
Help texte de second niveau (aide)

La même pile est disponible côté succès dans connection.Result (voir NTiConnection).

ToString et StackTrace : sans pile d'appels

Sur toute la hiérarchie, StackTrace est vide et ToString() est rendu SANS pile d'appels : le produit livré est obfusqué et la pile ne fait pas partie de la surface de diagnostic (politique v4). L'information actionnable est le message et la chaîne des causes internes (InnerException), toujours préservée.

Annulation : connexion cassée par contrat

Annuler le jeton d'une opération en vol lève OperationCanceledException portant le jeton de l'APPELANT (pas une exception NTi) et casse la connexion PAR CONTRAT : la trame en vol est perdue, la connexion n'est jamais réutilisée ni rendue au pool. Rouvrir la connexion (ou en reprendre une du pool) avant de continuer.

using System;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Aumerial.Data.Nti;

class ExceptionDemo
{
    static async Task Main()
    {
        await using var connection = new NTiConnection("server=MYIBMI;user=MYUSER;password=MYPASSWORD");
        await connection.OpenAsync();

        try
        {
            await connection.ExecuteClCommandAsync("DLTLIB LIB(NOPE)");
        }
        catch (NTiCommandException e)                    // échec CL ou programme
        {
            Console.WriteLine($"Code retour {e.ReturnCode}");
            foreach (var m in e.Messages)                // pile de messages IBM i
                Console.WriteLine($"{m.Id} [{m.Severity}] {m.Text}");
        }

        try
        {
            using var command = connection.CreateCommand();
            command.CommandText = "SELECT * FROM MYLIB.NOPE";
            await using var reader = await command.ExecuteReaderAsync();
        }
        catch (NTiSqlException e)                        // erreur SQL : SQLCODE négatif
        {
            Console.WriteLine($"SQLCODE {e.SqlCode}, SQLSTATE {e.SqlState}");
            Console.WriteLine(e.SecondLevelMessage);
        }
        catch (DbException e)                            // filet générique : tout NTi dérive de DbException
        {
            Console.WriteLine(e.Message);                // ToString() et Message sont rendus sans pile
        }

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        try
        {
            using var slow = connection.CreateCommand();
            slow.CommandText = "CALL MYLIB.LONGPROC()";
            await slow.ExecuteNonQueryAsync(cts.Token);
        }
        catch (OperationCanceledException)               // annulation : connexion cassée par contrat
        {
            // La trame en vol est perdue : rouvrir la connexion avant de continuer.
        }
    }
}

Reconnexion au serveur...

La connexion au serveur a été perdue. La page va se recharger.