L'IBM i est le meilleur système d’information, et cela n’est plus à démontrer. Mais la volonté de continuer à prendre en charge toutes les techniques des machines qui l’ont précédé l’oblige à supporter des ressources qui, si à une certaine époque étaient très utiles, sont aujourd’hui devenues des bizarreries.
La bibliothèque QTEMP, héritée de l'AS/400 et du S/38, en est un exemple. Elle est créée systématiquement au démarrage de tout job et supprimée lorsque celui-ci s'arrête. QTEMP appartient en exclusivité au job qui l'a créée, et elle n'est accessible que par ce qui s'exécute à l'intérieur de celui-ci.
Si les choses sont claires dans une utilisation classique de l'IBM i via les sous-systèmes QINTER et QBATCH, cela se complique lorsque les ressources de l'IBM i sont sollicitées via le sous-système QUSRWRK par les jobs de type QZDASOINIT et QZRCSRVS.
Exemple de Code
La séquence C# ci-dessous fournit exactement le même résultat : la duplication d’une table dans QTEMP, avec la création de TARTICL1 par une commande CPYF puis TARTICL2 par un ordre SQL CREATE TABLE.
Ce code utilise NTi Data Provider.
Try
{
cx.ExecuteClCommand("CPYF FROMFILE(AIRELLES/TARTICL) TOFILE(QTEMP/TARTICL1) MBROPT(*REPLACE) CRTFILE(*YES)");
cx.Execute("CREATE TABLE QTEMP.TARTICL2 AS (SELECT * FROM AIRELLES.TARTICL) WITH DATA");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
💡Un point d’arrêt a été placé sur le dernier bracket.
À l'exécution, lorsque le processus atteint le point d'arrêt, on constate sur l'IBM i la présence de deux jobs dans QUSRWRK via la commande WRKACTJOB SBS(QUSRWRK) : QZDASOINIT et QZRCSRVS.

QZDASOINITest dédié aux opérations sur la base de données via SQL.QZRCSRVSprend en charge toutes les opérations typiquement AS/400 : commandes CL, programmes, etc.
Examen de la structure de ces deux jobs
Liste de bibliothèques de QZDASOINIT :

Liste de bibliothèques de QZRCSRVS :

Dans les deux cas, une bibliothèque QTEMP est présente. En examinant leur contenu via l'option 5 de WRKACTJOB :
La bibliothèque QTEMP de QZDASOINIT contient le fichier TARTICL2 :

La bibliothèque QTEMP de QZRCSRVS contient le fichier TARTICL1 :

Conséquences
C'est là que les choses se compliquent.
Exécution de la requête SQL :
SELECT * FROM QTEMP.TARTICL1
⚠️ Message : fichier TARTICL1 non trouvé dans la bibliothèque QTEMP.
Exécution de la commande CL :
CPYF FROMFILE(QTEMP/TARTICL2) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)**
⚠️ Message : fichier TARTICL2 non trouvé dans la bibliothèque QTEMP.
Exécution de la requête SQL :
SELECT * FROM QTEMP.TARTICL2
✅ Tout se termine normalement.
Exécution de la commande CL :
CPYF FROMFILE(QTEMP/TARTICL1) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)
✅ Aucune erreur.
Le problème vient du fait qu'une requête SQL s'adresse à la QTEMP associée à QZDASOINIT, tandis que les commandes CL, programmes et autres ressources AS/400 utilisent la QTEMP du job QZRCSRVS.
Comme l'utilisation de QTEMP est aujourd'hui passée de mode, ce sont souvent des applications anciennes qui y ont recours. Les données utiles se retrouvent presque toujours dans la QTEMP de QZRCSRVS, donc inaccessibles par une requête SQL.
En réutilisant des programmes RPG, COBOL ou CLP existants depuis .NET, des cas de ce type peuvent survenir. Une solution consiste à utiliser un programme CLP intermédiaire pour lire le fichier dans la bonne QTEMP et remonter les données en C#. Mais cela implique d'apprendre à écrire du CL pour résoudre un problème d'infrastructure.
Une solution
La fonction C# suivante reçoit en paramètres la connexion IBM i préalablement établie (cx), le nom du fichier QTEMP à exploiter (QtempFile), et retourne les données dans une DataTable.
Pour clarifier les commentaires, les notations suivantes indiquent à quelle QTEMP s'adresse chaque instruction :
QZDASOINIT/QTEMPQZRCSRVS/QTEMP
Cette fonction déplace le fichier souhaité de QZRCSRVS/QTEMP vers QZDASOINIT/QTEMP pour le rendre accessible via une requête SQL.
public static DataTable QtempDt (NTiConnection cx, string QtempFile)
{
var adapter = new NTiDataAdapter();
DataTable dt = new DataTable();
var cmd = cx.CreateCommand();
cmd.CommandText = "SELECT * FROM QTEMP." + QtempFile;
adapter.SelectCommand = cmd;
// La récupération des données se fait via SQL : le fichier QtempFile
// sera recherché dans la QTEMP associée au job QZDASOINIT.
try
{
// Si le fichier a été créé par une requête SQL, cette instruction
// s'exécute avec succès (fichier présent dans QZDASOINIT/QTEMP).
adapter.Fill(dt);
}
catch
{
// Sinon, il faut copier le fichier depuis QZRCSRVS/QTEMP.
// Une bibliothèque temporaire est créée avec un nom unique
// basé sur l'heure courante (HHMMSS) pour éviter les doublons.
string BibTemporaire = "LB" +
DateTime.Now.ToString().Substring(11).Replace(":", "");
cx.ExecuteClCommand("CRTLIB LIB(" + BibTemporaire + ")");
// Utilise QZRCSRVS/QTEMP : copie du fichier dans la bib temporaire
cx.ExecuteClCommand("CPYF FROMFILE(QTEMP/" + QtempFile + ") TOFILE(" +
BibTemporaire + "/" + QtempFile + ") MBROPT(*REPLACE) CRTFILE(*YES)");
// Création du fichier dans QZDASOINIT/QTEMP avec copie des données
cx.Execute("CREATE TABLE QTEMP." + QtempFile + " AS (SELECT * FROM " +
BibTemporaire + "." + QtempFile + ") WITH DATA");
// Suppression de la bibliothèque temporaire
cx.ExecuteClCommand("DLTLIB LIB(" + BibTemporaire + ")");
// Les données sont intégrées à la DataTable
adapter.Fill(dt);
// Suppression du fichier QtempFile dans QZDASOINIT/QTEMP
cx.Execute("DROP TABLE QTEMP." + QtempFile);
}
return dt;
}
Sa rapidité d'exécution permet d'insérer cette succession d'instructions sans pénaliser les temps de réponse.
Bonus
Tout au long de cet article, les jobs QZDASOINIT et QZRCSRVS ont été évoqués. Ces jobs sont des travaux à démarrage anticipé : une fois démarrés, ils restent à l'état *WAIT, disponibles pour accueillir une nouvelle requête. Ils sont sollicités par ODBC, OleDb, ACS, et bien d'autres, dont NTi Data Provider.
Cet état *WAIT est utile pour compenser la latence des outils externes : le job est prêt dans l'IBM i, il suffit de le réveiller. La capture ci-dessous montre les deux jobs après une requête depuis un programme C# utilisant OleDb :
La capture d’écran ci-dessous montre les deux jobs ayant servi à une requête à partir d’un programme en C# qui utilise OleDb :

Les deux jobs sont à l'état *TIMW, prêts pour une nouvelle demande. Le problème : la QTEMP de la demande précédente est toujours présente, avec les incohérences que cela peut entraîner.
Normalement, ces jobs disparaissent après un certain temps d'inactivité. Mais il arrive qu'ils restent indéfiniment à l'état *TIMW sans être réutilisés, provoquant un empilement dans QUSRWRK difficile à gérer.
Ce risque n'existe pas avec NTi Data Provider.
Les jobs QZDASOINIT et QZRCSRVS démarrés par NTi ne passent jamais à l'état *TIMW ou *PSRW lorsque la session C# est terminée. Ils sont systématiquement détruits, même lors d'un arrêt brutal du programme, et redémarrés et réinitialisés à chaque nouvelle session. Il n'y a aucun risque de réutilisation ultérieure, sans impact sur les performances.
Laurent Rouillot