Exécuter une commande CL IBM i (AS/400) en C# (.NET) avec NTi
Introduction
Ce tutoriel montre comment exécuter une commande CL sur un IBM i (AS/400) depuis une application C# (.NET) en utilisant NTi Data Provider.
Les commandes CL (Control Language) permettent d'interagir directement avec le système IBM i pour automatiser des actions comme la création de bibliothèques, la gestion des objets ou l'exécution de traitements batch.
Grâce à NTi, il est possible d'exécuter ces commandes sans passer par une interface 5250, directement depuis du code .NET moderne, en asynchrone comme en synchrone.
Étape 1 - Ouvrir la connexion
Déclarez une instance de NTiConnection et ouvrez-la. Avec NTi, l'asynchrone est la voie normale (async réel de bout en bout), Open() reste disponible en synchrone.
using Aumerial.Data.Nti;
await using var conn = new NTiConnection("server=MY_SYSTEM;user=MY_USER;password=MY_PASSWORD");
await conn.OpenAsync();
💡
await usinggarantit que la connexion sera automatiquement fermée et libérée à la fin du bloc, même en cas d'erreur.
Étape 2 - Exécuter une commande CL
Utilisez la méthode ExecuteClCommandAsync() de NTiConnection (ou ExecuteClCommand() en synchrone) pour exécuter une commande CL :
await conn.ExecuteClCommandAsync("CRTLIB LIB(MYLIB) TEXT('My new library')");
Comme partout dans NTi, la variante asynchrone accepte un CancellationToken de l'appelant : await conn.ExecuteClCommandAsync(command, cancellationToken);
Étape 3 - Gérer les erreurs avec NTiCommandException
Quand une commande CL échoue, NTi lève une NTiCommandException qui embarque le code retour du serveur de commandes (ReturnCode) et la pile complète des messages IBM i dans Messages. Chaque message expose notamment Id (par exemple CPF2111), Severity et Text, mais aussi Type, File, Library, SubstitutionData et Help.
try
{
await conn.ExecuteClCommandAsync("CRTLIB LIB(MYLIB) TEXT('My new library')");
}
catch (NTiCommandException ex)
{
Console.WriteLine($"Commande en échec, code retour {ex.ReturnCode}");
foreach (var message in ex.Messages)
{
Console.WriteLine($"{message.Id} [{message.Severity}] {message.Text}");
}
}
Si la bibliothèque existe déjà, la pile de messages contient par exemple :
CPF2111 [40] La bibliothèque MYLIB existe déjà.
💡
NTiExceptionreste la classe de base commune (dérivée deDbException) : uncatch (NTiException)final attrape aussi les erreurs SQL (NTiSqlException) et réseau (NTiCommunicationException).
Deux jobs, deux QTEMP, deux CURLIB
Chaque NTiConnection ouvre en réalité deux jobs sur l'IBM i :
- le job SQL (
QZDASOINIT) exécute tout ce qui passe parNTiCommand; - le job commande (
QZRCSRVS) exécuteExecuteClCommand,CallProgrametCallServiceProgram.
Chaque job possède sa propre QTEMP et sa propre CURLIB : un objet créé dans QTEMP par une commande CL n'est pas visible du SQL, et une commande qui change la CURLIB du job commande ne change rien pour le job SQL.
// Le job commande crée un fichier source dans SA QTEMP
await conn.ExecuteClCommandAsync("CRTSRCPF FILE(QTEMP/DEMO)");
// Le job SQL a SA PROPRE QTEMP : DEMO n'y existe pas
await using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT COUNT(*) FROM QTEMP.DEMO";
try
{
await cmd.ExecuteScalarAsync();
}
catch (NTiSqlException ex)
{
Console.WriteLine(ex.Message); // SQL0204 : DEMO dans QTEMP de type *FILE introuvable
}
Pour inspecter les deux jobs (pratique pour les retrouver dans WRKACTJOB ou dans les logs) :
Console.WriteLine($"Job SQL : {conn.DatabaseJob}");
Console.WriteLine($"Job commande : {conn.CommandJob}");Le pont QSYS2.QCMDEXC
Pour exécuter une commande CL dans le job SQL (créer un objet dans la QTEMP vue par SQL, poser un OVRDBF qui s'applique aux requêtes, changer sa CURLIB), passez par la procédure SQL QSYS2.QCMDEXC :
// Exécute la commande CL DANS le job SQL (QZDASOINIT)
await using var bridge = conn.CreateCommand();
bridge.CommandText = "CALL QSYS2.QCMDEXC('CRTSRCPF FILE(QTEMP/DEMO)')";
await bridge.ExecuteNonQueryAsync();
// Cette fois, le job SQL voit l'objet : il vit dans SA QTEMP
await using var query = conn.CreateCommand();
query.CommandText = "SELECT COUNT(*) FROM QTEMP.DEMO";
var count = await query.ExecuteScalarAsync();
Ce pont a un surcoût : la commande transite par la voie SQL (préparation et exécution d'un CALL), et en cas d'échec l'erreur remonte en NTiSqlException générique, sans la pile de messages détaillée de NTiCommandException. Réservez QSYS2.QCMDEXC aux commandes qui doivent absolument agir sur l'environnement du job SQL. Pour tout le reste, ExecuteClCommandAsync est plus direct et mieux diagnostiqué.
Récapitulatif
Code complet (Program.cs d'une application console .NET 8) pour exécuter une commande CL depuis .NET avec NTi :
using Aumerial.Data.Nti;
await using var conn = new NTiConnection("server=MY_SYSTEM;user=MY_USER;password=MY_PASSWORD");
await conn.OpenAsync();
try
{
await conn.ExecuteClCommandAsync("CRTLIB LIB(MYLIB) TEXT('My new library')");
Console.WriteLine("Bibliothèque créée");
}
catch (NTiCommandException ex)
{
Console.WriteLine($"Commande en échec, code retour {ex.ReturnCode}");
foreach (var message in ex.Messages)
{
Console.WriteLine($"{message.Id} [{message.Severity}] {message.Text}");
}
}Et maintenant ?
- Appeler un programme : appel de programme RPG avec paramètres d'entrée/sortie
- Procédure stockée : appel de procédure stockée SQL avec Dapper et DataReader
- Appeler une API système : appel d'une API IBM i système via un User Space