- Extension method e ottimizzazione dell'accesso ai database -
 
COSA SERVE PER QUESTO TUTORIAL
 DownloadChiedi sul FORUM | Glossario cognizioni basiche di VB .Net
Come rendere aggiungere metodi a classi preesistenti

GLI EXTENSION METHOD
Come funziona l'attributo Extension.

Con Visual Basic 2008 (e C# 3.0) tra le varie novità ve ne è una semplice da utilizzare, comoda ma soprattutto elegante. Si tratta degli "extension method", ovvero dei metodi (o più precisamente Sub e Function) che "estendono" le funzionalità di una classe, ovvero che potranno essere chiamate come se fossero metodi di quella classe. A designare la classe da estendere, e a dare un nome a quello che, se il metodo fosse definito all'interno della classe, sarebbe la variabile Me è il primo parametro che il metodo riceve. Ad esempio, mettiamo di voler scrivere una funzione che prenda una stringa e la restituisca con una lettera maiuscola e una minuscola alternatamente, normalmente la stenderemmo alla maniera seguente:


Public Module ExtensionMethods

    Public Function ToLowerUpper(ByVal str As String) As String
        If str Is Nothing Then Return Nothing
        Dim result As String = String.Empty
        For C1 As Integer = 0 To result.Length - 1
            If C1 Mod 2 = 0 Then
                result &= result.ToUpper
            Else
                result &= result.ToLower
            End If
        Next C1
    
        Return result
    End Function

End Module

E verrebbe chiamato come segue:


Dim miaStringa As String = "Mia stringa!"
Console.WriteLine(ToLowerUpper(miaStringa))

Invece se aggiungiamo al metodo ToLowerUpper l'attributo Extension (definito nel namespace System.Runtime.CompilerServices) come segue:


Imports System.Runtime.CompilerServices

Public Module ExtensionMethods

    <Extension()> _
    Public Function ToLowerUpper(ByVal str As String) As String
        ' [...]
    End Function

End Module

Nota: gli extension method possono essere definiti solamente all'interno di moduli.
Potremo chiamarlo direttamente come se fosse un metodo della classe String:


Dim miaStringa As String = "Mia stringa!"
Console.WriteLine(miaStringa.ToLowerUpper())
Console.WriteLine("Altra stringa".ToLowerUpper())

Per meglio comprendere l'utilità di questa nuova funzionalità creiamo alcuni utili extension method per utilizzare più comodamente System.Data.

SEMPLIFICARE L'ACCESSO AD UN DATABASE
Come estendere la classe IDbConnection per rendere più agevole l'esecuzione di query. 

Effettuare una query su un database (o comunque una fonte dati) è un'operazione che viene svolta molto frequentemente, tuttavia le classi del Framework .Net non sono molto coincise, infatti, una volta aperta correttamente la connessione, bisogna creare un oggetto DbCommand (o DbDataAdapter) passandogli la connessione, la stringa SQL, creare un DataSet (o una DataTable) se necessario, impostare tutti i parametri e infine chiamare il metodo per eseguire la query (DbCommand.ExecuteNonQuery o DbDataAdapter.Fill).
Definiamo dunque alcuni semplici extension method sull'oggetto connessione che ci permettano di eseguire query, anche con parametri in una sola riga. Agiremo principalmente su IDbConnection (definito in System.Data), l'interfaccia che rappresenta una connessione, così da scrivere codice che sia generico e valido per qualsiasi driver si utilizzi (OdbcConnection, OleDbConnection, SqlConnection, MySqlConnection e così via).
Per prima cosa creiamo un extension method che sia un overload del metodo IDbConnection.CreateCommand che crea un'istanza di IDbCommand (OdbcCommand, OleDbCommand, SqlCommand o così via) che popoli il nuovo oggetto con il testo della query SQL e i suoi parametri.


<Extension()> _
Public Function CreateCommand(ByVal Connection As IDbConnection, ByVal Statement As String) As IDbCommand
    Dim command As IDbCommand = Connection.CreateCommand()
    command.CommandText = Statement
    Return command
End Function

In questa prima versione i parametri non sono contemplati, e non risulta pertanto molto utile, ci risparmia solamente una o due righe di codice.
Ecco come usarla:


Dim miaConnessione As IDbConnection = New OdbcConnection("Stringa di connessione")
miaConnessione.Open()

Dim mioComando As IDbCommand = miaConnessione.CreateCommand("INSERT INTO Users(Name) VALUES(""Paul"")")

mioComando.ExecuteNonQuery()
miaConnessione.Close()

Nella versione in grado di gestire i parametri però dobbiamo definire una struttura tramite la quale descrivere i parametri (nome, tipo, valore e così via), la struttura DbParameter:


Public Structure DbParameter
    Public _value As Object
    Public _name As String
    Public _dbType As DbType
    Public _size As Integer
    Private _parameterCount As Integer

    Public Sub New(ByVal Name As String, ByVal Value As Object, ByVal DbType As DbType, ByVal Size As Integer)
        _value = Value
        _dbType = DbType
        _name = Name
        _size = Size
        _parameterCount = 0
    End Sub
    Public Sub New(ByVal Name As String, ByVal Value As Object)
        Me.New(Name, Value, GetDbType(Value), Nothing)
    End Sub

    Public Sub New(ByVal Name As String, ByVal Value As Object, ByVal DbType As DbType)
        Me.New(Name, Value, DbType, Nothing)
    End Sub

    Public Shared Function GetDbType(ByVal Value As Object) As DbType
        Select Case True
            Case TypeOf Value Is Int16
                Return DbType.Int16
            Case TypeOf Value Is Int32
                Return DbType.Int32
            Case TypeOf Value Is Int64
                Return DbType.Int64
            Case TypeOf Value Is String
                Return DbType.String
            Case TypeOf Value Is Byte()
                Return DbType.Binary
            Case Else
                Throw New ArgumentException("A parameter can be only an Int16, Int32, Int64, Byte[] or String", _
                    "Parameters")
        End Select
    End Function

    Public Function CreateParameter(ByVal Command As IDbCommand) As IDbDataParameter
        Dim parameter As IDbDataParameter = Command.CreateParameter
        parameter.DbType = _dbType
        parameter.Value = _value
        parameter.Size = _size
        If String.IsNullOrEmpty(_name) Then
            parameter.ParameterName = "p" & _parameterCount.ToString
            _parameterCount += 1
        Else
            parameter.ParameterName = _name
        End If
        Return parameter
    End Function

End Structure

Il costruttore minimale di questa struttura riceve come parametri il nome e il valore, il tipo viene dedotto da una semplice funzione (GetDBType) che supporta alcuni basilari e comuni tipi di dato (interi, stringhe e array di byte).
Un'altra funzione invece si occupa di creare l'oggetto IDbDataParameter con le informazioni a disposizione, in modo che possano essere usate dall'oggetto IDbCommand. Dunque, tornando al nostro extension method avremo quanto segue:


<Extension()> _
Public Function CreateCommand(ByVal Connection As IDbConnection, ByVal Statement As String, _
    ByVal ParamArray Parameters As DbParameter()) As IDbCommand

    Dim command As IDbCommand = Connection.CreateCommand()
    command.CommandText = Statement
    For Each parameter As DbParameter In Parameters
        command.Parameters.Add(parameter.CreateParameter(command))
    Next parameter

    Return command
End Function
 

Ecco come chiamare la funzione:


Dim miaConnessione As IDbConnection = New OdbcConnection("Stringa di connessione")
Dim mioComando As IDbCommand 
miaConnessione.Open()

mioComando = miaConnessione.CreateCommand("INSERT INTO Users(Name, Occupation) VALUES(?nome, ?occupazione)", _
    New DbParameter("?nome", "Paul"), New DbParameter("?occupazione", "Direttore"))

mioComando.ExecuteNonQuery()
miaConnessione.Close()

Un ultimo overload di CreateCommand ci permette di specificare direttamente il parametro, senza istanziare la struttura DbParameter esplicitamente, ma sott'intendendo un nome del parametro del tipo "?pN" (dove N è un numero crescente maggiore di 1) e il tipo, che verrà automaticamente dedotto dal costruttore minimale e quindi dalla funzione DbParameter.GetDBType:


<Extension()> _
Public Function CreateCommand(ByVal Connection As IDbConnection, ByVal Statement As String, _
    ByVal ParamArray Parameters As Object()) As IDbCommand

    Dim command As IDbCommand = Connection.CreateCommand()
    command.CommandText = Statement
    For C1 As Integer = 0 To Parameters.Length - 1
        Dim parameter As Object = Parameters(C1)
        command.Parameters.Add(New DbParameter("?p" & (C1 + 1), parameter).CreateParameter(command))
    Next C1
    Return command
End Function

È quindi possibile utilizzarlo come segue:


Dim miaConnessione As IDbConnection = New OdbcConnection("Stringa di connessione")
miaConnessione.Open()
Dim mioComando As IDbCommand 

mioComando = miaConnessione.CreateCommand("INSERT INTO Users(Name) VALUES(?p1)", "Paul")

mioComando.ExecuteNonQuery()
miaConnessione.Close()

A questo punto possiamo fare un altro passo e definire il metodo IDbConnection.Execute che in un sol passaggio crea ed esegue la query, senza coinvolgere esplicitamente alcun IDbCommand:


<Extension()> _
Public Sub Execute(ByVal Connection As IDbConnection, ByVal Statement As String, _
    ByVal ParamArray Parameters As Object())

    Connection.CreateCommand(Statement, Parameters).ExecuteNonQuery()
End Sub
 

Che potrà quindi essere utilizzata come segue:


Dim miaConnessione As IDbConnection = New OdbcConnection("Stringa di connessione")
miaConnessione.Open()

miaConnessione.Execute("INSERT INTO Users(Name) VALUES(?p1)", "Paul")

miaConnessione.Close()
 

EXTENSION METHOD PER QUERY DI SELEZIONE
Metodi per riempire un DataSet, una DataTable o un oggetto comodamente. 

Vediamo ora come usare qualche altro extension method che coinvolge query di selezione che permette di riempire un DataSet, una DataTable o un semplice oggetto in una sola linea di codice.
Il sorgente può essere trovato nel file allegato, e poiché non viene fatto uso di DbDataAdapter, il codice resta generico e valido per qualsiasi driver si utilizzi (tramite il metodo spiegato in un precedente articolo).


Dim miaConnessione As IDbConnection = New OdbcConnection("Stringa di connessione")
miaConnessione.Open()

miaConnessione.Execute("INSERT INTO Users(Name) VALUES(?p1)", "Paul")
Dim dtResult As DataTable, dsResult As DataSet, lastId As Integer

dsResult = miaConnessione.FillDataSet("SELECT * FROM Users WHERE Name = ?p1 AND Surname = ?p2", _
    "Giovanni", "Rossi")

dtResult = miaConnessione.FillDataTable("SELECT MessageID FROM Messages WHERE Author = ?autore", _
    New DbParameter("autore", "Carlo", DbType.String))

lastId = miaConnessione.FillObject(Of Integer)("SELECT LAST_INSERT_ID()")

miaConnessione.Close()
 

 

<< INDIETRO by VeNoM00