NTiConnection

NTiConnection est la connexion ADO.NET du provider NTi pour IBM i (DB2 for i). Elle dérive de DbConnection et en honore intégralement le contrat standard (Open/OpenAsync, CreateCommand, BeginTransaction, ChangeDatabase, GetSchema, Close/DisposeAsync...), qui n'est pas redocumenté ici : voir la référence DbConnection. Cette page couvre la surface spécifique NTi : configuration, commandes CL, appels de programmes et de programmes de service, MFA, diagnostic et pool. L'async est la voie normale ; chaque méthode existe aussi en synchrone.

Propriétés de configuration

Chaque propriété typée correspond à un mot-clé de la chaîne de connexion ; défauts, synonymes et sémantique détaillée sont dans Propriétés de connexion. La configuration est figée dès que la connexion est ouverte : tout se règle avant OpenAsync (ou Open) ; seul le schéma courant se change à chaud, avec ChangeDatabase/ChangeDatabaseAsync. Par défaut (« persist security info » à faux), ConnectionString est expurgée du mot de passe dès l'ouverture.

Propriétés Rôle
Server, Username, Password serveur et identité IBM i
AdditionalFactor facteur MFA statique (voir la famille MFA plus bas)
LicenseLibrary bibliothèque de la licence NTi
UseSSL, Untrusted TLS vers les serveurs hôtes ; Untrusted (tests uniquement) accepte tout certificat
UsePortMapper, SignonPort, DatabasePort, CommandPort, MapperPort ports fixes ou résolus par le port mapper
Compress compression RLE des grandes réponses
Pooling, PoolSize, LimitPoolSize pool de connexions, INACTIF par défaut (pooling=true fortement recommandé en web)
DefaultCcsid, ForceTranslate, TrimCharFields, LegacyNullSemantics texte, CCSID et sémantique des NULL
DecfloatRoundMode arrondi DECFLOAT, voir DecfloatRoundOption
LOBMaxSize seuil (octets) de bascule des LOB en locators
BlockingFactor, FetchBlockSize, FetchAhead blocs de fetch (facteur adaptatif par défaut)
DefaultDatabase, NamingConvention schéma courant et nommage, voir NamingConvention
ApplicationName, ClientAccounting, ClientUserIdentifier, ClientProgramIdentifier identité client, intégrée à la clé de pool
ConnectionTimeout (lecture seule) délai d'établissement en secondes ; 0 = infini (défaut, comme tous les timeouts NTi)

Commandes CL et appels de programmes

Chaque connexion ouvre deux jobs IBM i distincts : le SQL s'exécute dans le job database (QZDASOINIT), les commandes CL et les appels de programmes dans le job commande (QZRCSRVS). Chaque job a son propre QTEMP, sa propre CURLIB et ses propres overrides : un objet créé en QTEMP par une commande CL n'est pas visible du SQL, et réciproquement. Pour exécuter une commande CL DANS le job SQL, passer par CALL QSYS2.QCMDEXC('...'), au prix de l'aller-retour SQL.

Méthode Rôle
ExecuteClCommand(command) et ExecuteClCommandAsync(command, ct) exécute une commande CL ; échec = NTiCommandException avec la pile de messages
CallProgram(library, program[, parameters]) et CallProgramAsync(..., ct) appelle un programme ou une API système ; paramètres : List ou IList (voir NTiProgramParameter)
CallServiceProgram(library, serviceProgram, procedureName, parameters[, returnFormat, procedureNameCcsid]) et CallServiceProgramAsync(..., ct) appelle une procédure exportée d'un programme de service via l'API QZRUCLSP

CallServiceProgram : le nom d'export est SENSIBLE À LA CASSE (comparé octet à octet dans le CCSID procedureNameCcsid, 37 par défaut) ; chaque paramètre passe par référence (défaut) ou par valeur selon son ServiceProgramParameterFormat (par valeur : BINARY(4) de 4 octets exactement) ; 7 paramètres au maximum. La méthode rend le porteur de la valeur de retour, ou null quand returnFormat vaut None : lire rc.GetInt() pour l'entier, et rc.GetInt(4) pour l'errno en IntegerAndErrno. Les retours pointeur ne sont pas supportés (un pointeur d'espace serveur n'a pas de sens côté client) : une procédure qui rend du texte remplit un buffer fourni par l'appelant.

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Aumerial.Data.Nti;

class NTiConnectionDemo
{
    static async Task Main()
    {
        await using var connection = new NTiConnection(
            "server=MYIBMI;user=MYUSER;password=MYPASSWORD;pooling=true");
        await connection.OpenAsync();            // Open() reste disponible en synchrone

        // Commande CL : job commande (QZRCSRVS)
        await connection.ExecuteClCommandAsync("OVRDBF FILE(ORDERS) TOFILE(MYLIB/ORDERS)");

        // API système = programme ordinaire : QWCRSVAL (valeurs système)
        var parms = new List
        {
            new NTiProgramParameter().AsInputOutput(),                   // récepteur vide
            new NTiProgramParameter(32, ParameterDirection.Input),       // longueur du récepteur
            new NTiProgramParameter(1, ParameterDirection.Input),        // nombre de valeurs
            new NTiProgramParameter("QSRLNBR", 10, ParameterDirection.Input),
            new NTiProgramParameter("", 8, ParameterDirection.Output),   // code erreur
        };
        await connection.CallProgramAsync("QSYS", "QWCRSVAL", parms);
        Console.WriteLine(parms[0].GetString(24, 8, 37).Trim());         // numéro de série

        // Procédure exportée d'un programme de service (QZRUCLSP)
        var buffer = new NTiProgramParameter(new byte[64], ParameterDirection.InputOutput);
        var length = new NTiProgramParameter(64)
        {
            ServiceProgramParameterFormat = NTiServiceProgramParameterFormat.ByValue,
        };
        var rc = await connection.CallServiceProgramAsync("QSYS", "QSOSRV1", "gethostname",
            new List { buffer, length },
            NTiServiceProgramReturnFormat.Integer);
        if (rc is not null && rc.GetInt() == 0)
            Console.WriteLine(buffer.GetString().TrimEnd('\0'));         // nom d'hôte du serveur

        // Deux jobs IBM i distincts par connexion
        Console.WriteLine($"Job SQL      : {connection.DatabaseJob} (CCSID {connection.DatabaseJobCcsid})");
        Console.WriteLine($"Job commande : {connection.CommandJob} (CCSID {connection.CommandJobCcsid})");
        Console.WriteLine($"Licence : {connection.RemainingDays} jours restants");

        // Purge du pool
        NTiConnection.ClearPool(connection);   // le pool correspondant à ces options
        NTiConnection.ClearAllPools();         // tous les pools du processus
    }
}

Famille MFA

Membre Rôle
AdditionalFactor facteur statique (code TOTP), prioritaire sur les deux rappels
AdditionalFactorProvider rappel synchrone Func<NTiAuthenticationFactorContext, string?>, invoqué au signon de chaque session physique ; prime sur la variante asynchrone
AdditionalFactorAsyncProvider rappel asynchrone Func<NTiAuthenticationFactorContext, CancellationToken, ValueTask<string?>> (coffres, HSM, prompts) ; sur OpenAsync, attendu nativement avec le jeton de l'APPELANT
AdditionalFactorCallback obsolète (compat v4, sans contexte) : préférer AdditionalFactorProvider
SupportsAdditionalFactor vrai quand le serveur connecté supporte l'authentification multifacteur

Le contexte (NTiAuthenticationFactorContext) identifie l'hôte (Host) et le profil (User) en cours d'authentification. Le rappel est invoqué une fois par session physique (le facteur est réutilisé pour les sockets de la session) et possiblement en concurrence pendant la croissance du pool. Rendre null poursuit le signon sans facteur : le serveur tranche.

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

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

        // Variante asynchrone : appelée au signon de chaque session physique, avec
        // le jeton de l'appelant sur OpenAsync. Un facteur statique
        // (connection.AdditionalFactor = "123456") ou le rappel synchrone
        // (AdditionalFactorProvider) ont priorité s'ils sont définis.
        connection.AdditionalFactorAsyncProvider = async (ctx, ct) =>
        {
            await Task.Yield();                  // coffre, HSM ou prompt : annulable par ct
            return Environment.GetEnvironmentVariable($"TOTP_{ctx.User}");
        };

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        await connection.OpenAsync(cts.Token);
        Console.WriteLine(connection.SupportsAdditionalFactor
            ? "Le serveur supporte la MFA"
            : "Signon sans facteur supplémentaire");
    }
}

Diagnostic et état

Membre Rôle
Result NTiMessage : résultat de la dernière opération (SQL, CL ou programme), succès comme échec ; porte SqlCode, SqlState, MessageID, MessageText, SecondLevelText et Messages (pile détaillée pour CL/programmes)
Messages compatibilité v4 : utiliser Result
DatabaseJob / CommandJob nom du job database (QZDASOINIT) / du job commande (QZRCSRVS), pour inspection côté serveur
DatabaseJobCcsid / CommandJobCcsid CCSID de chacun des deux jobs
RemainingDays jours restants de la licence NTi (0 si inconnu ou non licencié)

Pool

Deux statiques de purge, symétriques de SqlConnection : ClearPool(connection) vide le pool correspondant aux options de la connexion passée, ClearAllPools() vide tous les pools du processus (voir la fin du premier exemple). Le pool est inactif par défaut (compat v4) ; l'identité client (ApplicationName, ClientAccounting, ClientUserIdentifier, ClientProgramIdentifier) fait partie de la clé de pool : deux identités distinctes ne partagent jamais un pool.

Reconnexion au serveur...

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