Gestione dei conflitti QTEMP su IBM i in C# con NTi

Questo articolo si concentra sulle complicazioni che si incontrano quando i programmi moderni (come le applicazioni C#) interagiscono con sistemi legacy come IBM i, dove le funzioni legacy come QTEMP continuano a funzionare in modo isolato dagli altri componenti del sistema.

immagine illustrativa dell'articolo “Gestione dei conflitti QTEMP su IBM i in C# con NTi”

Introduzione

L'IBM i è il miglior sistema informativo, e questo non è più in discussione. Ma il desiderio di continuare a supportare tutte le tecniche delle macchine che lo hanno preceduto significa che deve supportare risorse che, sebbene un tempo molto utili, sono ormai diventate delle stranezze.

La libreria QTEMP, ereditata dall'AS400 e dall'S/38, ne è un esempio. Non mi dilungherò sul suo funzionamento; basti sapere che viene sistematicamente creata all'inizio di ogni lavoro e cancellata al termine dello stesso. Il QTEMP appartiene esclusivamente al lavoro che lo ha creato** e solo a quel lavoro, e vi si può accedere solo da ciò che è in esecuzione all'interno di quel lavoro.

Se le cose sono chiare in un AS400 che utilizza l'IBM i tramite i sottosistemi QINTER e QBATCH, le cose si complicano quando le risorse dell'IBM i vengono utilizzate tramite il sottosistema QUSRWRK da lavori di tipo QZDASOINIT e QZRCSRVS. Lo spiegherò con un esempio in C#.

Esempio di codice

La seguente sequenza di codice C# dà esattamente lo stesso risultato: la duplicazione di una tabella in QTEMP, con la creazione di TARTICL1 mediante un comando CPYF e poi di TARTICL2 mediante un comando SQL CREATE TABLE (questo codice utilizza 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());
}

💡 È stato impostato un punto di interruzione sull'ultima parentesi.

In fase di esecuzione, quando il processo raggiunge il punto di interruzione, notiamo su IBM i la presenza di due lavori in QUSRWRK grazie al comando WRKACTJOB SBS(QUSRWRK): QZDASOINIT e QZRCSRVS.

article 11

QZDASOINIT è dedicato alle operazioni di database tramite SQL.

QZRCSRVS gestisce tutte le operazioni tipiche di AS400, come i comandi CL, i programmi, ecc.

Esame della struttura di questi due lavori

Di seguito è riportato l'elenco delle librerie di QZDASOINIT:

article 11

E quella di QZRCSRVS:

article 11

In entrambi i casi, si nota la presenza di una libreria QTEMP.

Esaminiamo il contenuto di queste librerie utilizzando l'opzione 5 di WRKACTJOB.

La libreria QTEMP di QZDASOINIT contiene il file TARTICL2:

article 11

QTEMP di QZRCSRVS contiene il file TARTICL1 :

article 11

Conseguenze

Qui le cose si complicano.

  • Se eseguo la query SQL:
select * from qtemp.tarticl1

⚠️ Ricevo il messaggio « TARTICL1 file not found in the QTEMP library ».

  • Se eseguo il comando CL
CPYF FROMFILE(QTEMP/TARTICL2) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)**

⚠️ Ricevo il messaggio « TARTICL2 file not found in the QTEMP library ».

  • Se eseguo la query SQL :
select * from qtemp.tarticl2

Tutto si conclude normalmente.

  • Se eseguo il comando CL :
CPYF FROMFILE(QTEMP/TARTICL1) TOFILE(QGPL/TARTICL2) MBROPT(*REPLACE) CRTFILE(*YES)

Non c'è alcun errore.

Il problema deriva dal fatto che una query SQL è indirizzata alla libreria QTEMP associata a QZDASOINIT, mentre i programmi, i comandi e altre risorse puramente AS400 utilizzano la QTEMP del job QZRCSRVS.

Poiché l'uso della libreria QTEMP è passato di moda, molto spesso sono le applicazioni più vecchie a utilizzarla (prima della diffusione di SQL). I dati utili si trovano quasi sempre nella QTEMP di QZRCSRVS e sono quindi inaccessibili tramite una query SQL.

Come si può notare, se si vuole modernizzare riutilizzando programmi RPG, COBOL, CLP esistenti in .NET, ci si imbatte in casi in cui questo non funziona. È possibile utilizzare un programma CLP che legge il file dal QTEMP corretto e restituisce i dati in un programma C#. Ma, proprio per questo problema, dovrete insegnare al vostro sviluppatore .NET come scrivere codice CL.

Una soluzione

La seguente funzione C# riceve come parametri la definizione della connessione all'IBM i precedentemente stabilita (cx), il file QTEMP da utilizzare (QtempFile) e restituisce i dati in una DataTable. È possibile utilizzare altri mezzi per ricevere i dati.

Per semplificare i commenti, utilizzerò le seguenti notazioni per esprimere a quale libreria QTEMP è indirizzata l'esecuzione di un'istruzione:

  • QZDASOINIT/QTEMP
  • QZRCSRVS/QTEMP

Questa funzione sposta il file desiderato da QZRCSRVS/QTEMP a QZDASOINIT/QTEMP in modo da potervi accedere tramite una query SQL.

public static DataTable QtempDt (NTiConnection cx, string QtempFile)
{
    var adapter = new NTiDataAdapter(); // Definizione di un DataAdapter
    DataTable dt = new DataTable(); // Definizione della DataTable del risultato
    var cmd = cx.CreateCommand(); // Definizione del comando nella connessione NTi

    cmd.CommandText = "SELECT * FROM QTEMP." + QtempFile; // Composizione della query SQL con il nome del file
    adapter.SelectCommand = cmd; // Associare il comando al DataAdapter

    // ** I dati vengono recuperati utilizzando una query SQL, pertanto il file QtempFile verrà cercato nella libreria QTEMP associata al lavoro QZDASOINIT.

    try
    {
        // Se il file che si sta cercando è stato creato su IBM i da una query SQL, l'istruzione seguente viene eseguita con successo (il file QtempFile è presente in QZDASOINIT/QTEMP). 
        // I dati vengono integrati nella DataTable
        adapter.Fill(dt); 
    }
    catch 
    {
    // In caso contrario, copiare il file in QZRCSRVS/QTEMP.
    // Per farlo, abbiamo bisogno di una libreria intermedia. 
    // Qui, per evitare duplicazioni, creo una libreria il cui nome è costituito dalla concatenazione dei caratteri « LB » e dell'orario corrente (HHMMSS).
    // Possiamo fare le cose in modo diverso con le funzioni casuali

        string BibTemporaire = "LB" +         
        DateTime.Now.ToString().Substring(11).Replace(":", "");
        cx.ExecuteClCommand("CRTLIB LIB(" + BibTemporaire + ")");
        
        // Utilizza QZRCSRVS/QTEMP: il file viene copiato nel database temporaneo.
        cx.ExecuteClCommand("CPYF FROMFILE(QTEMP/" + QtempFile + ") TOFILE(" +     
        BibTemporaire + "/" + QtempFile + ") MBROPT(*REPLACE) CRTFILE(*YES)");
        
        // il file viene creato con una copia dei dati in QZDASOINIT/QTEMP. Si può accedere a questo QTEMP con una query SQL

        cx.Execute("CREATE TABLE QTEMP." + QtempFile + " AS (SELECT * FROM " + 
        BibTemporaire + "." + QtempFile + ") WITH DATA");

        
        // La libreria temporanea viene eliminata
        cx.ExecuteClCommand("DLTLIB LIB(" + BibTemporaire + ")");

        // I dati vengono integrati nella DataTable
        adapter.Fill(dt);

        // il file QtempFile viene cancellato in 
        cx.Execute("Drop table qtemp." + QtempFile);
    }

    // Viene restituita la DataTable
    return dt;
}

Questo programma utilizza naturalmente il connettore NTi Data Provider.

La sua velocità di esecuzione consente di inserire questa successione di istruzioni senza penalizzare i tempi di risposta.

Bonus

In questo articolo abbiamo parlato dei lavori QZDASOINIT e QZRCSRVS. Questi lavori sono normalmente noti come lavori ad avvio precoce. In altre parole, una volta avviati, rimangono nello stato « WAIT », disponibili a ricevere una nuova richiesta sull'IBM i. Questi lavori sono richiesti da ODBC, OleDb, ACS e molti altri, come un certo « NTi Data Provider ». Questo stato di « WAIT » è molto utile per compensare la lentezza degli strumenti esterni all'IBM i: il lavoro è pronto nell'IBM i, tutto ciò che si deve fare è risvegliarlo con una richiesta da un prodotto di terze parti.

La schermata seguente mostra i due lavori utilizzati per una query da un programma C# che utilizza OleDb :

article 11

Entrambi i lavori sono nello stato « TIMW », pronti a ricevere una nuova richiesta. Il problema è che la libreria QTEMP della richiesta precedente è ancora presente... Vi lascio immaginare le incongruenze che ne derivano.

Normalmente, dopo un certo periodo di tempo senza essere utilizzati, questi lavori scompaiono. A volte, però, questi lavori rimangono indefinitamente nello stato « TIMW » senza essere riutilizzati; in QUSRWRK si trova quindi un mucchio di righe di cui non si sa cosa fare: fermarli, conservarli... Consultare siti su Internet non aiuta.

Questo rischio non esiste con NTi Data Provider, perché i lavori QZDASOINIT e QZRCSRVS avviati da NTi non passano mai allo stato « TIMW » o « PSRW » quando la sessione C# viene terminata. Vengono sistematicamente distrutti, anche quando il programma in corso viene interrotto bruscamente (e quindi riavviato e reinizializzato ogni volta). Non c'è il rischio di riutilizzarli in un secondo momento.

E questo riavvio non ha alcun impatto sulle prestazioni di .NET con NTi.

Laurent Rouillot