Skip to content

7. Accesso al database

7.1. Informazioni generali

Esistono molti database disponibili per le piattaforme Windows. Per accedervi, le applicazioni utilizzano programmi chiamati driver.

Nel diagramma sopra riportato, il driver dispone di due interfacce:

  • l'interfaccia I1 presentata all'applicazione
  • l'interfaccia I2 verso il database

Per evitare che un'applicazione scritta per il database B1 debba essere riscritta in caso di migrazione a un database diverso B2, sono stati compiuti sforzi di standardizzazione sull'interfaccia I1. Se si utilizzano database che impiegano driver "standardizzati", al database B1 verrà fornito il driver P1, al database B2 il driver P2, e l'interfaccia I1 di questi due driver sarà identica. Pertanto, non sarà necessario riscrivere l'applicazione. Ad esempio, è possibile migrare un database ACCESS a un database MySQL senza modificare l'applicazione.

Esistono due tipi di driver standardizzati:

  • Driver ODBC (Open DataBase Connectivity)
  • Driver OLE DB (Object Linking and Embedding DataBase)

I driver ODBC consentono l'accesso ai database. Le origini dati per i driver OLE DB sono più varie: database, sistemi di posta elettronica, directory, ecc. Non ci sono limiti. Qualsiasi origine dati può essere oggetto di un driver OLE DB se un fornitore decide di farlo. Il vantaggio è ovviamente significativo: si ha un accesso uniforme a un'ampia varietà di dati.

La piattaforma .NET include due tipi di classi di accesso ai dati:

  1. Classi SQL Server.NET
  2. Classi OLE DB.NET

Le prime classi consentono l'accesso diretto al DBMS SQL Server di Microsoft senza un driver intermedio. Le seconde classi consentono l'accesso alle origini dati OLE DB.

Image

La piattaforma .NET è fornita (a maggio 2002) con tre driver OLE DB rispettivamente per SQL Server, Oracle e Microsoft Jet (Access). Se si desidera lavorare con un database che dispone di un driver ODBC ma non di un driver OLE DB, non è possibile. Pertanto, non è possibile lavorare con il DBMS MySQL, che (a maggio 2002) non fornisce un driver OLE DB. Tuttavia, esiste un insieme di classi che consentono l'accesso alle origini dati ODBC: le classi odbc.net. Queste non sono incluse di default nell'SDK e devono essere scaricate dal sito web di Microsoft. Negli esempi che seguono, utilizzeremo principalmente queste classi ODBC poiché la maggior parte dei database su Windows è dotata di un driver di questo tipo. Ecco, ad esempio, un elenco dei driver ODBC installati su un computer con Windows 2000 (Menu Start/Impostazioni/Pannello di controllo/Strumenti di amministrazione):

Image

Selezionare l'icona Origine dati ODBC:

Image

7.2. I due modi per utilizzare un'origine dati

La piattaforma .NET consente di utilizzare un'origine dati in due modi diversi:

  1. modalità connessa
  2. modalità disconnessa

In modalità connessa, l'applicazione

  1. apre una connessione alla fonte dati
  2. opera con la fonte dati in modalità di lettura/scrittura
  3. chiude la connessione

In modalità offline, l'applicazione

  1. apre una connessione alla fonte dati
  2. recupera una copia in memoria di tutti o parte dei dati dalla fonte
  3. chiude la connessione
  4. lavora con la copia in memoria dei dati in modalità di lettura/scrittura
  5. al termine del lavoro, apre una connessione, invia i dati modificati alla fonte dei dati in modo che possano essere aggiornati e chiude la connessione

In entrambi i casi, è il processo di elaborazione e aggiornamento dei dati a richiedere tempo. Immaginate che questi aggiornamenti vengano eseguiti da un utente che inserisce dati; questo processo può richiedere decine di minuti. Durante tutto questo tempo, in modalità connessa, la connessione al database viene mantenuta e le modifiche vengono applicate immediatamente. In modalità offline, non c'è connessione al database mentre i dati vengono aggiornati. Le modifiche vengono apportate solo alla copia in memoria. Vengono applicate all'origine dati tutte in una volta quando tutto è terminato.

Quali sono i vantaggi e gli svantaggi dei due metodi?

  • Una connessione consuma risorse di sistema. Se ci sono molte connessioni simultanee, la modalità offline aiuta a ridurne al minimo la durata. Questo è il caso delle applicazioni web con migliaia di utenti.
  • Lo svantaggio della modalità offline è la gestione delicata degli aggiornamenti simultanei. L'utente U1 recupera i dati al momento T1 e inizia a modificarli. Al momento T2, anche l'utente U2 accede alla fonte dati e recupera gli stessi dati. Nel frattempo, l'utente U1 ha modificato alcuni dati ma non li ha ancora trasmessi alla fonte dati. U2 sta quindi lavorando con dati, alcuni dei quali sono errati. Le classi .NET offrono soluzioni per gestire questo problema, ma non è facile risolverlo.
  • In modalità connessa, gli aggiornamenti simultanei dei dati da parte di più utenti non rappresentano normalmente un problema. Poiché la connessione al database viene mantenuta, è il database stesso a gestire questi aggiornamenti simultanei. Pertanto, Oracle blocca una riga nel database non appena un utente la modifica. Rimarrà bloccata — e quindi inaccessibile agli altri utenti — fino a quando l'utente che l'ha modificata non confermerà la modifica o la annullerà.
  • Se i dati devono essere condivisi in rete, è necessario scegliere la modalità offline. Questa modalità fornisce un'istantanea dei dati in un oggetto chiamato dataset, che funziona come un database autonomo. Questo oggetto può essere condiviso in rete tra macchine.

Esamineremo innanzitutto la modalità connessa.

7.3. Accesso ai dati in modalità connessa

7.3.1. I database nell'esempio

Stiamo considerando un database di Access denominato articles.mdb che contiene una sola tabella denominata ARTICLES con la seguente struttura:

nome
tipo
codice
Codice articolo di 4 caratteri
nome
il suo nome (stringa)
prezzo
il suo prezzo (effettivo)
stock_attuale
scorte attuali (numero intero)
min_stock
la scorta minima (numero intero) al di sotto della quale l'articolo deve essere rifornito

Il suo contenuto iniziale è il seguente:

Image

Utilizzeremo questo database sia tramite un driver ODBC che tramite un driver OLE DB per dimostrare la somiglianza tra i due approcci e poiché per ACCESS sono disponibili entrambi i tipi di driver.

Utilizzeremo anche un database MySQL denominato DBARTICLES, che contiene la stessa tabella ARTICLES, lo stesso contenuto ed è accessibile tramite un driver ODBC, per dimostrare che l'applicazione scritta per utilizzare il database Access non necessita di modifiche per utilizzare il database MySQL. Il database DBARTICLES è accessibile a un utente denominato admarticles con la password mdparticles. La schermata seguente mostra il contenuto del database MySQL:

C:\mysql\bin>mysql --database=dbarticles --user=admarticles --password=mdparticles
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3 to server version: 3.23.49-max-debug

Type 'help' for help.


mysql> show tables;
+----------------------+
| Tables_in_dbarticles |
+----------------------+
| articles             |
+----------------------+
1 row in set (0.01 sec)

mysql> select * from articles;
+------+--------------------------------+------+--------------+---------------+
| code | nom                            | prix | stock_actuel | stock_minimum |
+------+--------------------------------+------+--------------+---------------+
| a300 | vÚlo                           | 2500 |           10 |             5 |
| b300 | pompe                          |   56 |           62 |            45 |
| c300 | arc                            | 3500 |           10 |            20 |
| d300 | flÞches - lot de 6             |  780 |           12 |            20 |
| e300 | combinaison de plongÚe         | 2800 |           34 |             7 |
| f300 | bouteilles d'oxygÞne           |  800 |           10 |             5 |
+------+--------------------------------+------+--------------+---------------+
6 rows in set (0.02 sec)

mysql> describe articles;
+---------------+-------------+------+-----+---------+-------+
| Field         | Type        | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| code          | text        | YES  |     | NULL    |       |
| nom           | text        | YES  |     | NULL    |       |
| prix          | double      | YES  |     | NULL    |       |
| stock_actuel  | smallint(6) | YES  |     | NULL    |       |
| stock_minimum | smallint(6) | YES  |     | NULL    |       |
+---------------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

mysql> exit
Bye

Per definire il database ACCESS come origine dati ODBC, procedere come segue:

  • Aprire l'Amministratore delle origini dati ODBC come mostrato sopra e selezionare la scheda DSN utente (DSN = Data Source Name)

Image

  • Aggiungere una fonte utilizzando il pulsante Aggiungi, specificare che questa fonte è accessibile tramite un driver Access e fare clic su Fine:

Image

  • Assegnare alla fonte dati il nome "articles-access", inserire una descrizione a scelta e utilizzare il pulsante Seleziona per specificare il file .mdb del database. Terminare facendo clic su OK.

Image

La nuova origine dati apparirà quindi nell'elenco dei DSN utente:

Image

Per definire il database DBARTICLES di MySQL come origine dati ODBC, procedere come segue:

  • Aprire l'Amministratore delle origini dati ODBC come mostrato sopra e selezionare la scheda DSN utente. Aggiungere una nuova origine dati utilizzando Aggiungi e selezionare il driver ODBC di MySQL.

Image

  • Fare clic su Fine. Verrà quindi visualizzata una pagina di configurazione della sorgente MySQL:

54321

Image

  • Al punto (1), assegnare un nome all'origine dati ODBC
  • Al punto (2), specifica il computer su cui si trova il server MySQL. Qui inseriamo `localhost` per indicare che si trova sullo stesso computer della nostra applicazione. Se il server MySQL fosse su un computer remoto `M`, inseriremmo qui il suo nome e la nostra applicazione funzionerebbe con un database remoto senza alcuna modifica.
  • Al punto (3), inserisci il nome del database. In questo caso, si chiama DBARTICLES.
  • Al punto (4), inserisci il nome utente admarticles e al punto (5) la password mdparticles.

7.3.2. Utilizzo di un driver ODBC

In un'applicazione che utilizza un database in modalità connessa, sono generalmente necessari i seguenti passaggi:

  1. Connessione al database
  2. Invio di query SQL al database
  3. Ricezione ed elaborazione dei risultati di queste query
  4. Chiusura della connessione

I passaggi 2 e 3 vengono eseguiti ripetutamente; la connessione viene chiusa solo al termine delle operazioni sul database. Si tratta di un modello relativamente standard che potrebbe risultare familiare a chi ha già lavorato con un database in modo interattivo. Questi passaggi sono gli stessi sia che si acceda al database tramite un driver ODBC sia tramite un driver OLE DB. Di seguito è riportato un esempio che utilizza le classi .NET per la gestione delle origini dati ODBC. Il programma si chiama liste e accetta come parametro il nome DSN di un'origine dati ODBC contenente una tabella denominata ARTICLES. Quindi visualizza il contenuto di questa tabella:

dos>liste
syntaxe : pg dsnArticles

dos>liste articles-access

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

dos>liste mysql-artices
Erreur d'exploitation de la base de données (ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified)

dos>liste mysql-articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

Dai risultati sopra riportati, possiamo vedere che il programma ha elencato i contenuti sia del database ACCESS che del database MySQL. Esaminiamo ora il codice di questo programma:


' options
Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
 
Module db1
    Sub main(ByVal args As String())
        ' application console
        ' displays the contents of a ARTICLES table in a DSN database 
        ' whose name is passed in parameter
        Const syntaxe As String = "syntaxe : pg dsnArticles"
        Const tabArticles As String = "articles"        ' table of articles
 
        ' parameter verification
        ' do we have 1 parameter
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If
 
        ' parameter is retrieved
        Dim dsnArticles As String = args(0)                ' the DSN database
        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing        ' the connection
        Dim myReader As OdbcDataReader = Nothing            ' the data reader
 
        ' attempt to access the database
        Try
            ' base connection chain
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()
 
            ' execution of a SQL command
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()
 
            ' Using the recovered table
            ' column display
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
 
            ' data display
            While myReader.Read()
                ' current line operation
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While
        Catch ex As Exception
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données " + ex.Message + ")"))
            Environment.Exit(2)
        Finally
            ' drive lock
            myReader.Close()
            ' locking connection
            articlesConn.Close()
        End Try
    End Sub
End Module

Le classi di gestione delle origini ODBC si trovano nello spazio dei nomi Microsoft.Data.Odbc, che deve quindi essere importato. Inoltre, alcune classi si trovano nello spazio dei nomi System.Data.


Imports System.Data
Imports Microsoft.Data.Odbc

Gli spazi dei nomi utilizzati dal programma si trovano in assembly diversi. Compilare il programma utilizzando il seguente comando:

dos>vbc /r:microsoft.data.odbc.dll /r:microsoft.visualbasic.dll /r:system.dll /r:system.data.dll db1.vb

7.3.2.1. La fase di connessione

Una connessione ODBC utilizza la classe OdbcConnection. Il costruttore di questa classe accetta come parametro quella che viene chiamata "stringa di connessione". Si tratta di una stringa di caratteri che definisce tutti i parametri necessari per stabilire la connessione al database. Tali parametri possono essere numerosi, rendendo la stringa complessa. La stringa ha la forma "param1=valore1;param2=valore2;...;paramj=valorej;". Ecco alcuni possibili valori per paramj:

uid
nome utente dell'utente che accederà al database
password
password di questo utente
dsn
il nome DSN del database, se presente
origine dati
nome del database a cui si accede
...
 

Se si definisce un'origine dati come origine dati ODBC utilizzando l'Amministratore delle origini dati ODBC, questi parametri sono già stati specificati e salvati. In tal caso, è sufficiente passare il parametro DSN, che fornisce il nome DSN dell'origine dati. Ecco cosa si fa in questo caso:


        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing        ' the connection
        Dim myReader As OdbcDataReader = Nothing        ' the data reader
        Try
            ' attempt to access the database
            ' base connection chain
            Dim connectString As String = "DSN=" + dsnArticles + ";"
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()

Una volta creato l'oggetto OdbcConnection, apriamo la connessione utilizzando il metodo Open. Questa operazione potrebbe fallire, proprio come qualsiasi altra operazione sul database. Ecco perché l'intero codice di accesso al database è racchiuso in un blocco try-catch. Una volta stabilita la connessione, possiamo eseguire query SQL sul database.

7.3.2.2. Esecuzione di query SQL

Per eseguire query SQL, abbiamo bisogno di un oggetto Command, più specificatamente un oggetto OdbcCommand, poiché stiamo utilizzando un'origine dati ODBC. La classe OdbcCommand ha diversi costruttori:

  • OdbcCommand(): crea un oggetto Command vuoto. Per utilizzarlo, sarà necessario specificare in seguito varie proprietà:
    • CommandText: il testo della query SQL da eseguire
    • Connection: l'oggetto OdbcConnection che rappresenta la connessione al database su cui verrà eseguita la query
    • CommandType: il tipo della query SQL. Sono disponibili tre possibili valori
  1. CommandType.Text: la proprietà CommandText contiene il testo di una query SQL (valore predefinito)
  2. CommandType.StoredProcedure: la proprietà CommandText contiene il nome di una procedura memorizzata nel database
  3. CommandType.TableDirect: la proprietà CommandText contiene il nome di una tabella T. Equivalente a `SELECT * FROM T`. Esiste solo per i driver OLE DB.
  • OdbcCommand(string sqlText): il parametro sqlText verrà assegnato alla proprietà CommandText. Si tratta del testo della query SQL da eseguire. La connessione deve essere specificata nella proprietà Connection.
  • OdbcCommand(string sqlText, OdbcConnection connection): il parametro sqlText viene assegnato alla proprietà CommandText, mentre il parametro connection viene assegnato alla proprietà Connection.

Per eseguire la query SQL sono disponibili due metodi:

  • OdbcDataReader ExecuteReader(): invia la query SELECT da CommandText a Connection e crea un oggetto OdbcDataReader che fornisce l'accesso a tutte le righe nella tabella dei risultati della query SELECT
  • int ExecuteNonQuery(): invia la query di aggiornamento (INSERT, UPDATE, DELETE) da CommandText a Connection e restituisce il numero di righe interessate dall'aggiornamento.

Nel nostro esempio, dopo aver aperto la connessione al database, emettiamo una query SQL SELECT per recuperare il contenuto della tabella ARTICLES:


            ' exécution d'une commande SQL
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOdbcCommand As New OdbcCommand(sqlText)
            myOdbcCommand.Connection = articlesConn
            myReader = myOdbcCommand.ExecuteReader()

Una query è tipicamente del seguente tipo:

    select col1, col2,... from table1, table2,...
    where condition
    order by expression
    ...

Sono obbligatorie solo le parole chiave nella prima riga; le altre sono facoltative. Esistono altre parole chiave non riportate qui.

  1. Viene eseguito un join su tutte le tabelle elencate dopo la parola chiave `FROM`
  2. Vengono mantenute solo le colonne che seguono la parola chiave `select`
  3. Vengono mantenute solo le righe che soddisfano la condizione della parola chiave `where`
  4. Le righe risultanti, ordinate in base all'espressione nella parola chiave `ORDER BY`, costituiscono il risultato della query.

Il risultato di un SELECT è una tabella. Se consideriamo la precedente tabella ARTICLES e vogliamo i nomi degli articoli il cui stock attuale è inferiore alla soglia minima, scriveremmo:

    select nom from articles where stock_actuel<stock_minimum

Se vogliamo che siano ordinati in ordine alfabetico per nome, scriveremmo:

    select nom from articles where stock_actuel<stock_minimum order by nom

7.3.2.3. Lavorare con il risultato di una query SELECT

Il risultato di una query SELECT in modalità disconnessa è un oggetto DataReader, in questo caso un oggetto OdbcDataReader. Questo oggetto consente di recuperare in sequenza tutte le righe del risultato e di ottenere informazioni sulle colonne presenti in tali risultati. Esaminiamo alcune proprietà e alcuni metodi di questa classe:

FieldCount
il numero di colonne nella tabella
Item
Item(i) rappresenta la colonna numero i della riga corrente nel risultato
XXX GetXXX(i)
il valore della colonna i della riga corrente, restituito come tipo XXX (Int16, Int32, Int64, Double, String, Boolean, ...)
stringa GetName(i)
nome della colonna numero i
Chiudi()
chiude l'oggetto OdbcdataReader e libera le risorse associate
bool Read()
sposta in avanti di una riga nella tabella dei risultati. Restituisce false se ciò non è possibile. La nuova riga diventa la riga corrente del lettore.

L'elaborazione del risultato di un'istruzione SELECT è tipicamente un'operazione sequenziale simile a quella dei file di testo: è possibile solo avanzare nella tabella, non tornare indietro:


While myReader.Read()
    ' on a une ligne - on l'exploite
    ....
    ' ligne suivante
end while

Queste spiegazioni sono sufficienti per comprendere il codice seguente nel nostro esempio:


            ' Exploitation de la table récupérée
            ' affichage des colonnes
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
 
            ' affichage des données
            While myReader.Read()
                ' exploitation ligne courante
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While

L'unica difficoltà risiede nell'istruzione in cui vengono concatenati i valori delle diverse colonne della riga corrente:


                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i

La notazione line+=myReader(i).ToString viene tradotta in line+=myReader.Item(i).ToString(), dove Item(i) è il valore della colonna i nella riga corrente.

7.3.2.4. Rilascio delle risorse

Le classi OdbcReader e OdbcConnection dispongono entrambe di un metodo Close() che libera le risorse associate agli oggetti in fase di chiusura.

                ' fermeture lecteur
                myReader.Close()
                ' fermeture connexion
                articlesConn.Close()

7.3.3. Utilizzo di un driver OLE DB

Useremo lo stesso esempio, questa volta con un database a cui si accede tramite un driver OLE DB. La piattaforma .NET fornisce un driver di questo tipo per i database Access. Quindi useremo lo stesso database articles.mdb di prima. Il nostro obiettivo qui è mostrare che, sebbene le classi possano cambiare, i concetti rimangono gli stessi:

  • la connessione è rappresentata da un oggetto OleDbConnection
  • una query SQL viene emessa utilizzando un oggetto OleDbCommand
  • se questa query è un'istruzione SELECT, viene restituito un oggetto OleDbDataReader per accedere alle righe della tabella dei risultati

Queste classi si trovano nello spazio dei nomi System.Data.OleDb. Il programma precedente può essere facilmente adattato per funzionare con un database OLE DB:

  • Sostituire OdbcXX con OleDbXX ovunque
  • modificare la stringa di connessione. Per un database ACCESS senza login/password, la stringa di connessione è Provider=Microsoft.JET.OLEDB.4.0;Data Source=[file.mdb]. La parte configurabile di questa stringa è il nome del file ACCESS da utilizzare. Modificheremo il nostro programma in modo che accetti il nome di questo file come parametro.
  • Lo spazio dei nomi da importare è ora System.Data.OleDb.

Il nostro programma diventa il seguente:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
Imports System.Data.OleDb
 
Module db2
    Public Sub Main(ByVal args() As String)
 
        ' application console
        ' displays the contents of a ARRTICLES table in a DSN database 
        ' whose name is passed in parameter
        Const syntaxe As String = "syntaxe : pg base_access_articles"
        Const tabArticles As String = "articles"        ' table of articles
 
        ' parameter verification
        ' do we have 1 parameter
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If
 
        ' parameter is retrieved
        Dim dbArticles As String = args(0)        ' the database
 
        ' preparing the connection to the comic
        Dim articlesConn As OleDbConnection = Nothing        ' the connection
        Dim myReader As OleDbDataReader = Nothing        ' the data reader
 
        ' attempt to access the database
 
        Try
            ' base connection chain
            Dim connectString As String = "Provider=Microsoft.JET.OLEDB.4.0;Data Source=" + dbArticles + ";"
            articlesConn = New OleDbConnection(connectString)
            articlesConn.Open()
 
            ' execution of a SQL command
            Dim sqlText As String = "select * from " + tabArticles
            Dim myOleDbCommand As New OleDbCommand(sqlText)
            myOleDbCommand.Connection = articlesConn
            myReader = myOleDbCommand.ExecuteReader()
 
            ' Using the recovered table
            ' column display
            Dim ligne As String = ""
            Dim i As Integer
            For i = 0 To (myReader.FieldCount - 1) - 1
                ligne += myReader.GetName(i) + ","
            Next i
            ligne += myReader.GetName(i)
            Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
            ' data display
            While myReader.Read()
                ' current line operation
                ligne = ""
                For i = 0 To myReader.FieldCount - 1
                    ligne += myReader(i).ToString + " "
                Next i
                Console.WriteLine(ligne)
            End While
        Catch ex As Exception
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
            Environment.Exit(2)
        Finally
            ' drive lock
                myReader.Close()
                ' locking connection
                articlesConn.Close()
        End Try
        ' end
        Environment.Exit(0)
    End Sub
End Module

I risultati ottenuti:

dos>vbc liste.vb

E:\data\serge\MSNET\vb.net\adonet\6>dir
07/05/2002  15:09                2 325 liste.CS
07/05/2002  15:09                4 608 liste.exe
20/08/2001  11:54               86 016 ARTICLES.MDB

dos>liste articles.mdb

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

7.3.4. Aggiornamento di una tabella

Gli esempi precedenti si limitavano a elencare il contenuto di una tabella. Modificheremo il nostro programma di gestione del database dei prodotti in modo che possa modificare il database. Il programma si chiama sql. Passiamo come parametro il nome DSN del database dei prodotti da gestire. L'utente digita i comandi SQL direttamente sulla tastiera e il programma li esegue, come mostrano i seguenti risultati ottenuti dal database dei prodotti MySQL:

dos>vbc /r:microsoft.data.odbc.dll sql.vb

dos>sql mysql-articles

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

Requête SQL (fin pour arrêter) : select * from articles where stock_actuel<stock_minimum

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20

Requête SQL (fin pour arrêter) : insert into articles values ("1","1",1,1,1)
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5
1 1 1 1 1

Requête SQL (fin pour arrêter) : update articles set nom="2" where nom="1"
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5
1 2 1 1 1

Requête SQL (fin pour arrêter) : delete from articles where code="1"
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (fin pour arrêter) : select * from articles

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

a300 vélo                           2500 10 5
b300 pompe                          56 62 45
c300 arc                            3500 10 20
d300 flèches - lot de 6             780 12 20
e300 combinaison de plongée         2800 34 7
f300 bouteilles d'oxygène           800 10 5

Requête SQL (fin pour arrêter) : select * from articles order by nom asc

----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------

c300 arc                            3500 10 20
f300 bouteilles d'oxygène           800 10 5
e300 combinaison de plongée         2800 34 7
d300 flèches - lot de 6             780 12 20
b300 pompe                          56 62 45
a300 vélo                           2500 10 5

Requête SQL (fin pour arrêter) : fin

Il programma è il seguente:


' options
Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Data.OleDb
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
 
Module db3
    Public Sub Main(ByVal args() As String)
 
        ' application console
        ' executes SQL requests typed on the keyboard on a 
        ' table ARTICLES from a database DSN whose name is passed as a parameter
        Const syntaxe As String = "syntaxe : pg dsnArticles"
 
        ' parameter verification
        ' do we have 2 parameters
        If args.Length <> 1 Then
            ' error msg
            Console.Error.WriteLine(syntaxe)
            ' end
            Environment.Exit(1)
        End If        'if
        ' parameter is retrieved
        Dim dsnArticles As String = args(0)
        ' base connection chain
        Dim connectString As String = "DSN=" + dsnArticles + ";"
 
        ' preparing the connection to the comic
        Dim articlesConn As OdbcConnection = Nothing
        Dim sqlCommand As OdbcCommand = Nothing
        Try
            ' attempt to access the database
            articlesConn = New OdbcConnection(connectString)
            articlesConn.Open()
            ' create a command object
            sqlCommand = New OdbcCommand("", articlesConn)
            'try
        Catch ex As Exception
            ' error msg
            Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
            ' freeing up resources
            Try
                articlesConn.Close()
            Catch
            End Try
            Environment.Exit(2)
        End Try        'catch
        ' build a dictionary of accepted sql commands
        Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
        Dim dicoCommandes As New Hashtable
        Dim i As Integer
        For i = 0 To commandesSQL.Length - 1
            dicoCommandes.Add(commandesSQL(i), True)
        Next i
 
        ' read-execute SQL commands typed on the keyboard
        Dim requête As String = Nothing        ' query text SQL
        Dim champs() As String        ' query fields    
        Dim modèle As New Regex("\s+")
        ' input-execution loop for SQL commands typed on keyboard
        While True
            ' no error at start
            Dim erreur As Boolean = False
            ' request for query
            Console.Out.Write(ControlChars.Lf + "Requête SQL (fin pour arrêter) : ")
            requête = Console.In.ReadLine().Trim().ToLower()
            ' finished?
            If requête = "fin" Then
                Exit While
            End If
            ' the query is broken down into fields
            champs = modèle.Split(requête)
            ' valid request?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' error msg
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' following request
                erreur = True
            End If
            If Not erreur Then
                ' prepare the Command object to execute the request
                sqlCommand.CommandText = requête
                ' query execution
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If
                Catch ex As Exception
                    ' error msg
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try
            End If
        End While
        ' freeing up resources
        Try
            articlesConn.Close()
        Catch
        End Try
        Environment.Exit(0)
    End Sub
 
    ' execute an update request
    Sub executeUpdate(ByVal sqlCommand As OdbcCommand)
        ' executes sqlCommand, update request
        Dim nbLignes As Integer = sqlCommand.ExecuteNonQuery()
        ' display
        Console.Out.WriteLine(("Il y a eu " & nbLignes & " ligne(s) modifiée(s)"))
    End Sub
 
    ' executing a Select query
    Sub executeSelect(ByVal sqlCommand As OdbcCommand)
        ' executes sqlCommand, select query
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Using the recovered table
        ' column display
        Dim ligne As String = ""
        Dim i As Integer
        For i = 0 To (myReader.FieldCount - 1) - 1
            ligne += myReader.GetName(i) + ","
        Next i
        ligne += myReader.GetName(i)
        Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
        ' data display
        While myReader.Read()
            ' current line operation
            ligne = ""
            For i = 0 To myReader.FieldCount - 1
                ligne += myReader(i).ToString + " "
            Next i
            ' display
            Console.WriteLine(ligne)
        End While
        ' freeing up resources
        myReader.Close()
    End Sub
End Module

Qui ci limiteremo a commentare le novità rispetto al programma precedente:

  • Creiamo un dizionario dei comandi SQL accettati:

        ' on construit un dictionnaire des commandes sql acceptées
        Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
        Dim dicoCommandes As New Hashtable
        Dim i As Integer
        For i = 0 To commandesSQL.Length - 1
            dicoCommandes.Add(commandesSQL(i), True)
        Next i

Questo ci permette quindi di verificare semplicemente se la prima parola (fields[0]) della query digitata è uno dei quattro comandi accettati:


            ' requête valide ?
            If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
                ' msg d'erreur
                Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
                ' requête suivante
                erreur = True
            End If            'if
  • In precedenza, la query era stata suddivisa in campi utilizzando il metodo Split della classe RegEx:

        Dim modèle As New Regex("\s+")
....
            ' the query is broken down into fields
            champs = modèle.Split(requête)

Le parole nella query possono essere separate da un numero qualsiasi di spazi.

  • L'esecuzione di una query SELECT non utilizza lo stesso metodo dell'esecuzione di una query di aggiornamento (INSERT, UPDATE, DELETE). Pertanto, è necessario eseguire un controllo ed eseguire una funzione diversa per ciascuno di questi due casi:

                ' préparation de l'objet Command pour exécuter la requête
                sqlCommand.CommandText = requête
                ' exécution de la requête
                Try
                    If champs(0) = "select" Then
                        executeSelect(sqlCommand)
                    Else
                        executeUpdate(sqlCommand)
                    End If                    'try
                Catch ex As Exception
                    ' msg d'erreur
                    Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
                End Try

L'esecuzione di una query SQL può generare un'eccezione, che viene gestita in questa sede.

  • La funzione executeSelect copre tutto ciò che è stato trattato negli esempi precedenti.
  • La funzione executeUpdate utilizza il metodo ExecuteNonQuery della classe OdbcCommand, che restituisce il numero di righe interessate dal comando.

7.3.5. Calcolo delle imposte

Riutilizziamo l'oggetto tax creato in un capitolo precedente:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class impôt
    ' data required for tax calculation
    ' come from an external source
    Private limites(), coeffR(), coeffN() As Decimal
 
    ' manufacturer
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' we check that the 3 tablaeux are the same size
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' it's good
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub
 
    ' tAX CALCULATION
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' calculating the number of shares
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' calculation of taxable income & family quota
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' tAX CALCULATION
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' return result
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

Aggiungiamo un nuovo costruttore per inizializzare gli array limit, coeffR e coeffN da un database ODBC:


Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
...
 

    ' builder 2
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        ' initializes the three limit arrays, coeffR, coeffN from
        ' the contents of the Timpots table in the ODBC DSNimpots database
        ' colLimites, colCoeffR, colCoeffN are the three columns of this table
        ' can throw an exception
        Dim connectString As String = "DSN=" + DSNimpots + ";"        ' base connection chain
        Dim impotsConn As OdbcConnection = Nothing        ' the connection
        Dim sqlCommand As OdbcCommand = Nothing        ' the SQL command
        ' the SELECT query
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
        ' tables to retrieve data
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList
 
        ' attempt to access the database
        impotsConn = New OdbcConnection(connectString)
        impotsConn.Open()
        ' create a command object
        sqlCommand = New OdbcCommand(selectCommand, impotsConn)
        ' execute the query
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Using the recovered table
        While myReader.Read()
            ' the data of the current line are put in the tables
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        ' freeing up resources
        myReader.Close()
        impotsConn.Close()
 
        ' dynamic tables are placed in static tables
        Me.limites = New Decimal(tLimites.Count) {}
        Me.coeffR = New Decimal(tLimites.Count) {}
        Me.coeffN = New Decimal(tLimites.Count) {}
        Dim i As Integer
        For i = 0 To tLimites.Count - 1
            limites(i) = Decimal.Parse(tLimites(i).ToString())
            coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
            coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
        Next i
    End Sub

Il programma di test è il seguente: riceve come argomenti i parametri da passare al costruttore della classe tax. Dopo aver costruito un oggetto tax, calcola l'imposta dovuta:


Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
' test pg
Module testimpots
    Sub Main(ByVal arguments() As String)
        ' interactive tax calculator
        ' the user enters three data points on the keyboard: married nbEnfants salary
        ' the program then displays the tax payable
        Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
        Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
        ' checking program parameters
        If arguments.Length <> 5 Then
            ' error msg
            Console.Error.WriteLine(syntaxe1)
            ' end
            Environment.Exit(1)
        End If        'if
        ' retrieve the arguments
        Dim DSNimpots As String = arguments(0)
        Dim tabImpots As String = arguments(1)
        Dim colLimites As String = arguments(2)
        Dim colCoeffR As String = arguments(3)
        Dim colCoeffN As String = arguments(4)
 
        ' tax object creation
        Dim objImpôt As impôt = Nothing
        Try
            objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(2)
        End Try
 
        ' infinite loop
        While True
            ' initially no errors
            Dim erreur As Boolean = False
 
            ' tax calculation parameters are requested
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
 
            ' anything to do?
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If
 
            ' check the number of arguments in the input line
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe2)
                erreur = True
            End If
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Integer
            If Not erreur Then
                ' checking parameter validity
                ' married
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                    erreur = True
                End If
                ' nbEnfants
                nbEnfants = 0
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                    erreur = True
                End Try
                ' salary
                salaire = 0
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                    erreur = True
                End Try
            End If
            If Not erreur Then
                ' parameters are correct - tax is calculated
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
            End If
        End While
    End Sub
End Module

Il database utilizzato è un database MySQL con nome DSN mysql-imports:

C:\mysql\bin>mysql --database=impots --user=admimpots --password=mdpimpots
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5 to server version: 3.23.49-max-debug

Type 'help' for help.

mysql> show tables;
+------------------+
| Tables_in_impots |
+------------------+
| timpots          |
+------------------+

mysql> select * from timpots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+

L'esecuzione del programma di test produce i seguenti risultati:

dos>D:\data\devel\vbnet\poly\chap6\impots>vbc /r:system.data.dll /r:microsoft.data.odbc.dll /r:system.dll /t:library impots.vb

dos>vbc /r:impots.dll testimpots.vb

dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :