- Accedere a DBMS diversi con lo stesso codice -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario Un IDE per .Net - un server MySql - un server SqlServer (anche Express) - conoscenze basiche di VB .Net
Le interfacce comuni di System.Data

LE INTERFACCE IDBCONNECTION E IDBCOMMAND DI ADO.NET
Come accedere a diversi DBMS sacrificando i DbDataAdapter.

In questo articolo vedremo come accedere a tre DBMS diversi (Access, MySql e SqlServer ma in teoria anche tutti gli altri compatibili con ADO.NET) senza dover fare eccessive modifiche al nostro codice. Certo è comodo utilizzare i vari oggetti DBMS-specifici, ad esempio qualunque programmatore .Net sa quanto sia semplice usare DbDataAdapter (System.Data.Odbc.OdbcDataAdapter, System.Data.OleDb.OleDbDataAdapter o altri) per eseguire delle query SQL, ma se si utilizzano questi oggetti e si intende supportare DBMS diversi bisognerà mettere mettere blocchi If ovunque e questa non è certamente una buona pratica.
Proprio per questo esistono delle interfacce comuni che sono implementate dai vari client SQL, ad esempio System.Data.OleDb.OleDbConnection e System.Data.SqlClient.SqlConnection implementano System.Data.IDbConnection e così anche System.Data.OleDb.OleDbCommand e System.Data.Odbc.OdbcCommand implementano System.Data.IDbCommand. Questo ci permette di verificare una sola volta che DBMS si sta usando istanziare la connessione e poi utilizzare quest'ultimo oggetto attraverso l'interfaccia comune, in seguito da esso eseguiremo query senza problemi attraverso IDbCommand.
Vediamo innanzitutto la funzione che restituisce la connessione avendo come parametro un valore di un'enumerazione che indica le possibilità.


Public Enum SourceType
    MDB
    MySql
    SqlServer
End Enum
    
Private Function GetConnection(ByVal Source As SourceType) As IDbConnection
    Dim cntDBIndipendent As IDbConnection
    Select Case Source
        Case SourceType.MDB
            'Stringa di connessione OLEDB per un database Access
            cntDBIndipendent = New OleDb.OleDbConnection( _
                "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & GetEXEDirectory() & _
                "test.mdb")
        Case SourceType.MySql
            'Stringa di connessione ODBC per un database MySql
            'È necessario impostare la password manualmente
            cntDBIndipendent = New Odbc.OdbcConnection( _
                "DRIVER={MySQL ODBC 3.51 Driver};Server=arcadi67.startlogicmysql.com;Database=test;" & _
                "UID=root;pwd=pass;")
        Case SourceType.SqlServer
            'Stringa di connessione SqlConnection per un database SqlServer
            cntDBIndipendent = New SqlClient.SqlConnection( _
                "Data Source=FIGHTER\SQLEXPRESS;Initial Catalog=test;" & _
                "Integrated Security=True;")
    End Select

    'Tutti gli oggetti precedentemente usati implementano l'interfaccia IDbConnection:
    'lo restituiamo senza sapere quale sia il caso specifico.
    Return cntDBIndipendent
End Function

Il codice in questione è molto semplice, si tratta solo di vedere che sorgente di dati è stata richiesta e dunque istanziare la classe corretta (OleDbConnection, OdbcConnection o SqlConnection) con la relativa stringa di connessione. Il risultato viene poi messo in cntDBIndipendent, variabile di tipo IDbConnection, interfaccia, come già detto, implementata da tutti gli oggetti di connessione ADO.NET. IDbConnection è un'interfaccia molto semplice i cui metodi più interessanti sono Open(), Close() (che, come è ovvio, aprono e chiudono la connessione) ma sopratutto CreateCommand().


Public Function GetData(ByVal Source As SourceType) As DataSet
    'Otteniamo la connessione del tipo desiderato e la mettiamo 
    'in una variabile di tipo "neutro" (interfaccia indipendente
    'dal DBMS)
    Dim cntConnection As IDbConnection = GetConnection(Source)

    'Apriamo la connessione
    cntConnection.Open()

    'Creiamo l'oggetto che eseguirà il comando
    Dim cmdCommand As IDbCommand = cntConnection.CreateCommand()

    'Impostiamo il testo della query
    cmdCommand.CommandText = "SELECT * FROM Test"

    'Leggiamo tutti i dati dal DataReader e li mettiamo in un DataSet
    Dim dsResult As DataSet
    dsResult = FillDataSet(cmdCommand.ExecuteReader())

    'Chiudiamo la connessione
    cntConnection.Close()

    Return dsResult
End Function

CreateCommand(), come è facilmente intuibile crea un oggetto-comando, ovvero istanzia la classe che permette di eseguire query. Esso restituisce un oggetto IDbCommand, interfaccia implementata da OleDbCommand, OdbcCommand e SqlCommand, in questo modo potremo eseguire query su tutti questi (e altri) DBMS senza neppure sapere a quale ci stiamo riferendo.
Non è purtroppo possibile usare un DbDataAdapter in quanto non è possibile istanziarlo in alcun modo senza avere accesso diretto a una particolare classe derivata come OleDbDataAdapter o simili, e inoltre System.Data.Common.DbDataAdapter è MustInherit e non è dunque possibile chiamare il costruttore direttamente.
Dunque una volta che abbiamo il nostro oggetto di tipo IDbCommand non ci resta che impostarne la proprietà CommandText sulla query SQL desiderata (è anche a disposizione la proprietà Parameters per specificare parametri), aprire la connessione e invocare il metodo ExecuteReader (o ExecuteNonQuery nel caso non debbano essere restituiti dati, come nel caso di un'istruzione DELETE o UPDATE ma non SELECT). Nel codice sopra è possibile vedere che l'oggetto System.Data.IDataReader restituito da ExecuteReader è passato alla funzione FillDataSet: essa "converte" (ovvero legge e copia i dati da) un IDataReader ad un DataSet.


Private Function FillDataSet(ByVal rdrReader As System.Data.IDataReader) As DataSet
    Dim dsOutput As New DataSet
    'Ciclo per la lista delle tabelle
    Do
        Dim dtSchema As DataTable = rdrReader.GetSchemaTable()
        Dim dtBuffer As New DataTable
        If Not dtSchema Is Nothing Then
            'Ciclo della tabella contenente la lista delle colonne 
            'della tabella che sta per essere letta
            For CRows As Integer = 0 To dtSchema.Rows.Count - 1
                'Compila la descrizione della tabella che sta per essere letta
                Dim drBuffer As DataRow = dtSchema.Rows(CRows)
                Dim strColumnName As String = DirectCast(drBuffer("ColumnName"), String)
                'Crea la colonna con nome e tipo di dati forniti e la aggiunge alla 
                'lista delle colonne della tabella che sta per essere letta
                Dim clnBuffer As New DataColumn(strColumnName, _
                    DirectCast(drBuffer("DataType"), Type))
                dtBuffer.Columns.Add(clnBuffer)
            Next CRows

            'Aggiunge al DataSet la tabella, per ora vuota
            dsOutput.Tables.Add(dtBuffer)

            'Legge (e copia) dal DataReader riga per riga i valori 
            'dei campi della tabella corrente
            'e li copia nella DataTable appena creata
            Do While rdrReader.Read()
                Dim drBuffer As DataRow = dtBuffer.NewRow()
                'Aggiunge un campo alla volta nella colonna rispettiva
                For CFields As Integer = 0 To rdrReader.FieldCount - 1
                    drBuffer(CFields) = rdrReader.GetValue(CFields)
                Next CFields
                dtBuffer.Rows.Add(drBuffer)
            Loop
        End If
        'Continua il ciclo finchè non vi sono più tabelle
    Loop While rdrReader.NextResult()

    'Se non c'è alcuna tabella restituisce Nothing
    If dsOutput.Tables.Count > 0 Then Return dsOutput Else Return Nothing
End Function

Spiegare nel dettaglio questa funzione va oltre lo scopo di questo articolo, ma in sintesi essa legge una tabella alla volta, di ogni tabella prende la struttura attraverso il metodo GetSchemaTable di System.Data.IDataReader, prepara una DataTable e poi vi inserisce i dati forniti. Si viene così ad avere un DataSet completo di tutte le tabelle richieste dalla query con nomi di campo e tipi corretti. Solitamente questo compito di lettura viene delegato ad un DbDataAdapter, ma nel nostro caso, per ragioni già spiegate, questa soluzione non è percorribile.
In sostanza si è visto come accedere ad un database Access, MySql e SqlServer con piccole variazioni al codice. Questa tecnica funziona in generale con tutti i DBMS che supportano Odbc (vale l'esempio per MySql) ma anche per Oracle, il client ufficiale di MySql e tante altre librerie client ADO.NET.

 

<< INDIETRO by VeNoM00