Utilizzare le classi Qt per connettersi ad un database SQL Server 2012 ed eseguire query, salvando i dati su un file delimitato.

Ciao Developer!

In questo articolo ti spiegherò come utilizzare le Qt per connetterti ad un Database SqlServer, eseguire Query, e salvare i dati richiesti in un file di testo (è stato scelto il file di testo per semplicità di implementazione).

Giusto per cronaca, facciamo un pò di storia, per chi non avesse nessuna idea di cosa siano le Qt.

Le Qt sono, semplicemente, delle libreria multipiattaforma sviluppate a partire dal 1991 da Quasar Technologies, e sono ampiamente utilizzate nell'ambiente desktop KDE. Sono librerie per lo sviluppo di applicazioni con interfaccia grafica, e sfruttano il concetto di widget. Più approfondimenti possono essere trovati su Wikipedia, se invece desiderate scaricare l'editor e tutto l'SDK potete farlo al link del sito ufficiale delle Qt.

Detto questo, cominciamo.

Innanzi tutto bisognerà importare nel progetto il riferimento alle librerie specifiche di SQL, nel file di progetto, quindi andremo ad inserire la stringa

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += sql

Intuitivamente, si evince che abbiamo intenzione di importare le librerie per la gestione di SQL per le versioni delle Qt maggiori della 4. Dopo questo passaggio dovremo lanciare il comando run QMake dal menu Build, per generare il makefile che servirà poi alla compilazione corretta del programma.

 

creaiamo quindi 2 file, sqlserver_connect.h e sqlserver_connect.cpp che saranno i file di intestazione e implementazione della nostra classe. 

Aggiungiamo gli include necessari intesta al file header sqlserver_connect.h

#ifndef SQLSERVER_CONNECT_H
#define SQLSERVER_CONNECT_H

#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QFile>
#include <QFileDialog>

dopodichè passiamo alla creazione della classe vera e propria, che doteremo di costruttore e distruttore

#ifndef SQLSERVER_CONNECT_H
#define SQLSERVER_CONNECT_H

#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QFile>
#include <QFileDialog>

class sqlserver_connect : public QObject
{
    Q_OBJECT
public:
    explicit sqlserver_connect(QObject *parent = nullptr);
    ~sqlserver_connect();
};

#endif // SQLSERVER_CONNECT_H

Questo è lo scheletro della nostra classe, ora cominciamo ad inserire i dati di interesse. Visto che stiamo parlando di SqlServer, avremo bisogno di alcuni parametri di connessione, quali: il server SQLServer a cui connettersi, il nome del database da utilizzare, eventualeutilizzo dell'autenticazione di windows e, in caso negativo, il nome utente e password dell'utente che si vuol connettere al database. Inoltre dovremmo stabilire su che porta è in ascolto SqlServer (solitamente è la 1433, ma possiamo parametrizzare questo dato per dare alla nostra classe la maggiore elasticità ed adattamento possibile).

Definiamo quindi, come proprietà pubbliche della classe, i seguenti dati:

public:
    QString server; //server a cui connettersi
    QString userid; //nome utente
    QString password; //password
    QString port; //porta dove il server SQL è in ascolto
    QString dbName; //Nome del database da utilizzare
    bool windowsAuthentication; //utilizzo della modalità di autenticazione di windows

Ora, i puristi della programmazione in C++ potranno obiettare che, sia per ottenere il massimo della sicurezza (possibile), sia per una buona norma di progettazione delle classi, quello che si dovrebbe fare è dichiarare le proprietà come "private" e implementare il relativo metodo get e set per ottenere il valore e impostare il valore del dato. Tutto giusto, ma in questo caso, molto estremo, dal momento che non abbiamo bisogno di controlli particolari sui dati, e anche nel caso in cui questi fossero non corretti, la connessione fallirebbe semplicemente.

C'è invece una proprietà in sola lettura che renderemo privata, e cioè 

private:
   bool valid = false;

con il relativo metodo "get" isValid(), che recupererà il valore di questa variabile.

public:
    bool isValid();

Se sei arrivato a leggere fino qui, sappi che la scrittura di questi articoli, comporta dispendio di tempo e studio. Puoi dimostrarmi il tuo sostegno mettendo "Mi Piace alla" mia pagina!

 

Questa proprietà è quella che ci informa se al momento della connessione qualcosa non è andato a buon fine, e quindi tutte le altre funzionalità della classe, saranno eseguite previa verifica che la variabile valid sia true.

Ora creiamo una funzione openConnection(), che si occuperà di mettere insieme i valori delle proprietà precedenti, per formare la stringa del driver, e permettere così di aprire una connessione al Database SqlServer. Prima però dobbiamo dichiarare un'altra proprietà, privata, che è l'oggetto Database

public:
    void openConnection();
private:
    QSqlDatabase db = QSqlDatabase::addDatabase("QODBC3");

L'oggetto db, è un oggetto di tipo QSqlDatabase che gestirà il DBMS SqlServer (il codice QODBC3 è identifica il tipo di database Sql Server). Arrivati a questo punto, vediamo cosa fà la funzione openConnection(), che implementeremo nel file sqlserver_connect.cpp

void sqlserver_connect::openConnection()
{
    if (db.isOpen())
        return;

    if (server.isEmpty() || dbName.isEmpty() || (!windowsAuthentication && (userid.isEmpty() || password.isEmpty())))
    {
        valid=false;
        return;
    }
    else
    {
        try
        {
            QString connectionString = "DRIVER={SQL Server};Server=" + server +";Database="+dbName+";";
            if (windowsAuthentication)
                connectionString += "Trusted_Connection=yes;";
            else
                connectionString += "Uid=" + userid+";Pwd="+ password+";";

            if (!port.isEmpty())
                connectionString += "Port=" + port + ";";
            db.setDatabaseName(connectionString);
            if (db.open())
                valid = true;
        }
        catch(QException ex)
        {
            QMessageBox::warning(NULL,"","Database SQL Server non accessibile!",QMessageBox::Ok);

        }
    }

}

Questa funzione, banalmente, effettua un controllo preliminare sull'oggetto db per testare che non sia già aperto, successivamente sulla presenza dei dati di interesse per la connessione, controllando che le proprietà inserite inizialmente non siano vuote. Se tutto è ok, viene composta la stringa del driver per la connessione al database, controllando anche il fatto che sia richiesta la modalità di autenticazione di Windows o meno, e impostando, se presente, la porta di comunicazione.

La chiamata al metodo db.open() determina il cambio di stato della proprietà valid, se questa diventa true, vuol dire che la connessione è stabilita e tutte le altre funzionalità potranno essere eseguite.

 Passiamo ora all'esecuzione di una query sul database appena aperto, quindi definiamo il metodo "query" nel file di header e di implementazione

//sqlserver_connect.h
public:
        QSqlQuery *query(QString query);

//sqlserver_connect.cpp
QSqlQuery *sqlserver_connect::query(QString query)
{
    if (valid){
        QSqlQuery *rSet = new QSqlQuery();
        rSet->exec(query);
        return rSet;
    }
    return NULL;
}

questo metodo si occupa di eseguire una query sul database, utilizzando l'oggetto QSqlQuery rSet, e di restituire il puntatore ottenuto, oppure NULL se il database non ha potuto stabilire una connessione. Per reperire i valori dei campi dalla query eseguita, ho implementato un metodo "didattico", che semplicemente legge i dati dal recordset e li scrive su un file di testo delimitato con un carattere a scelta.

Vediamo l'implementazione del metodo, dopo averlo dichiarato nel file di header:

bool sqlserver_connect::saveQueryResultToTextFile(QSqlQuery* result, QString destFile, QString delimiter)
{
    try{
        QFile inputFile(destFile);

        if (inputFile.open(QIODevice::ReadWrite))
        {
            QTextStream stream(&inputFile);
            bool canProceed = result->first();
            int fieldsCount =     result->record().count();            //Numero di campi nel record

            while (canProceed)
            {
                for (int i = 0; i <fieldsCount; i++ )
                    stream << result->value(i).toString() << (i == (fieldsCount-1) ? "\r\n" : delimiter);
                canProceed = result->next();
            }
            inputFile.close();
            return true;
        }
    }
    catch(QException ex)
    {

    }
    return false;
}

A parte l'apertura del file di testo per scrivere i dati, la cosa importante è: sapere come accedere ai dati derivanti dalla query eseguita. Per farlo prima di tutto controlliamo che siano stati restituiti dei record, con il metodo "first()" dell'oggetto QSqlQuery. Se sì, allora possiamo controllare quanti campi ci sono in un record, tramite il metodo .count() dell'oggetto result->record().

Per accedere al valore dei campi utilizzeremo il metodo ->value() dell'oggetto QSqlQuery, che accetta come parametro o un indice numerico (secondo l'ordinamento dei campi fatto in fase di scrittura della query), oppure il nome del campo di cui vogliamo conoscere il valore. 

ATTENZIONE: il metodo value() restituisce dati di tipo QVariant, che è un tipo di dato variabile. Per convertirlo nel tipo di dato corretto, si potranno utilizzare i metodi "to<Tipo>()" dell'oggetto QVariant. In questo caso, utilizzerò "toString()" perchè desidero scrivere una stringa di testo sul file.

Bene, anche per oggi, abbiamo terminato, ti ricordo che in allegato puoi trovare la classe pronta all'uso in QtCreator (ricorda che ho lavorato con la versione 5.10.1 delle Qt).

Come sempre, un piccolo sostegno è ben gradito, mi aiuteresti a scrivere contenuti sempre più interessanti e soprattutto di qualità!

Donazione per 7software.it | 1 EUR