2. Parte 2
2.1. Introduction
Inizieremo ricordando ciò che è stato fatto nella parte 1 e, in particolare, l’architettura a tre livelli [web, domain, dao] utilizzata. Nella soluzione proposta, il livello [dao] era un livello di test: la fonte dei dati era implementata da un oggetto [ArrayList]. In questo articolo ci soffermeremo sul livello [dao], presentando diverse possibili implementazioni dello stesso quando i dati si trovano in un SGBD.
Strumenti utilizzati:
- il SGBD Firebird - cfr. allegato, paragrafo 3.5.
- il SGBD MSDE (Microsoft Data Engine) - vedi allegato, paragrafo 3.12.
- IBExpert, edizione personale per l’amministrazione grafica di SGBD Firebird - cfr. allegato, paragrafo 3.6.
- EMS MS SQL Manager per l'amministrazione grafica di SGBD MSDE - cfr. allegato, paragrafo 3.14.
- Ibatis SqlMap per il livello di accesso ai dati del SGBD - cfr. paragrafo 2.5.6.2.
In una scala che va da principiante a intermedio ad avanzato, questo documento rientra nella sezione [intermédiaire-avancé]. Per comprenderlo sono necessari diversi prerequisiti. Alcuni di essi possono essere acquisiti nei documenti che ho scritto. In tal caso, li cito. È ovvio che si tratta solo di un suggerimento e che il lettore può utilizzare i propri documenti preferiti.
- linguaggio VB.net: [Introduction au langage VB.NET par l'exemple ]
- programmazione web in VB.net: [Développement WEB avec ASP.NET 1.1 ]
- utilizzo dell’aspect IoC di Spring: [Spring IoC pour .NET ]
- Documentazione Ibatis SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
- Documentazione Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
- Documentazione Spring.net: [http://www.springframework.net/documentation.html]
2.2. L'applicazione webarticles - Riepilogo
Qui presentiamo gli elementi dell’applicazione web semplificata di e-commerce analizzata nella parte 1. Questa consente ai clienti del web di:
- di consultare un elenco di articoli provenienti da un database
- di inserirne alcuni in un carrello elettronico
- confermare il carrello. Tale conferma ha come unico effetto l’aggiornamento, nel database, delle scorte degli articoli acquistati.
2.2.1. Le viste dell'applicazione
Le diverse viste presentate all’utente sono le seguenti:
![]() |
![]() |
![]() |
- la schermata [ERREURS] che segnala eventuali errori dell'applicazione

2.2.2. Architettura generale dell'applicazione
L'applicazione realizzata nella parte 1 presenta un'architettura a tre livelli:
![]() |
- i tre livelli sono stati resi indipendenti grazie all’utilizzo di interfacce
- l'integrazione dei diversi livelli è stata realizzata con Spring
- ogni livello è associato a spazi dei nomi distinti: web (livello UI), domain (livello business) e dao (livello di accesso ai dati).
L'applicazione segue un'architettura MVC (Modello - Vista - Controller). Se riprendiamo lo schema a livelli riportato sopra, l'architettura MVC si integra in esso nel modo seguente:
![]() |
L'elaborazione di una richiesta da parte di un cliente si svolge secondo le seguenti fasi:
- il cliente invia una richiesta al controller. In questo caso, il controller è una pagina .aspx a cui viene assegnato un ruolo specifico. Essa riceve tutte le richieste dei clienti. È la porta d’ingresso dell’applicazione. È la C di MVC.
- il controller elabora questa richiesta. A tal fine, potrebbe aver bisogno dell’aiuto del livello di business, ovvero il modello M nella struttura MVC.
- Il controller riceve una risposta dal livello di business. La richiesta del cliente è stata elaborata. Ciò può comportare diverse possibili risposte. Un esempio classico è
- una pagina di errore se la richiesta non è stata elaborata correttamente
- una pagina di conferma in caso contrario
- il controller sceglie la risposta (= vista) da inviare al cliente. Si tratta, nella maggior parte dei casi, di una pagina contenente elementi dinamici. Il controller fornisce tali elementi alla vista.
- La vista viene inviata al cliente. È la V di MVC.
2.2.3. Il modello
Il modello M di MVC è costituito qui dai seguenti elementi:
- le classi di business
- le classi di accesso ai dati
- il database
2.2.3.1. Il database
Il database contiene solo una tabella denominata ARTICLES, generata con i seguenti comandi SQL:
CREATE TABLE ARTICLES (
ID INTEGER NOT NULL,
NOM VARCHAR(20) NOT NULL,
PRIX NUMERIC(15,2) NOT NULL,
STOCKACTUEL INTEGER NOT NULL,
STOCKMINIMUM INTEGER NOT NULL
);
/* vincoli */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* chiave primaria */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chiave primaria che identifica in modo univoco un articolo | |
nome dell'articolo | |
il suo prezzo | |
disponibilità attuale | |
la quantità di magazzino al di sotto della quale è necessario effettuare un ordine di rifornimento |
2.2.3.2. Gli spazi dei nomi del modello
Il modello M è fornito sotto forma di due spazi dei nomi:
- istia.st.articles.dao: contiene le classi di accesso ai dati del livello [dao]
- istia.st.articles.domain: contiene le classi di business del livello [domain]
Ciascuno di questi spazi dei nomi è contenuto in un proprio file "assembly":
assembly | contenu | rôle |
webarticles-dao | - [IArticlesDao]: l'interfaccia di accesso al livello [dao]. È l'unica interfaccia visibile al livello [domain]. Non ne vede altre. - [Article]: classe che definisce un articolo - [ArticlesDaoArrayList]: classe di implementazione dell’interfaccia [IArticlesDao] con una classe [ArrayList] | livello di accesso ai dati - si trova interamente nel livello [dao] dell'architettura a tre livelli dell'applicazione web |
webarticles-domain | - [IArticlesDomain]: l'interfaccia di accesso al livello [domain]. È l'unica interfaccia visibile al livello web. Non ne vede altre. - [AchatsArticles]: una classe che implementa [IArticlesDomain] - [Achat]: classe che rappresenta l’acquisto di un cliente - [Panier]: classe che rappresenta l'insieme degli acquisti di un cliente | rappresenta il modello degli acquisti sul web - si trova interamente nel livello [domain] dell’architettura a tre livelli dell’applicazione web |
2.2.4. Implementazione e test dell’applicazione [webarticles]
2.2.4.1. Distribuzione
Distribuiamo l’applicazione sviluppata nella parte 1 dell’articolo in una cartella denominata [runtime]:
![]() | ![]() |
![]() |
Note:
La cartella [runtime] contiene tre file e due sottocartelle:
- i controller [global.asax] e [main.aspx]
- il file di configurazione [web.config]
- la cartella [bin] che contiene:
- i file DLL dei tre livelli [webarticles-dao.dll], [webarticles-domain.dll], [webarticles-web.dll]
- i file necessari per Spring [Spring-Core.*], [log4net.dll]
- la cartella [vues] che contiene il codice di presentazione delle diverse viste.
- la presenza dei file di codice .vb è superflua poiché la loro versione compilata si trova in DLL.
2.2.4.2. Test
Configuriamo il server web [Cassini] nel modo seguente:

con:
Percorso fisico: D:\data\serge\lavoro\2004-2005\aspnet\webarticles-010405\runtime\
Percorso virtuale: /webarticles
Con un browser accediamo a URL [http://localhost/webarticles/main.aspx]

Ricordiamo che il livello [dao] è implementato da una classe che memorizza gli articoli in un oggetto [ArrayList]. Questa classe crea un elenco iniziale di quattro articoli. Partendo dalla vista sopra riportata, utilizziamo i collegamenti del menu per eseguire alcune operazioni. Eccone alcune. La colonna di sinistra rappresenta la richiesta del cliente e quella di destra la risposta che gli viene fornita.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. Il livello [dao] rivisitato
Nella nostra prima implementazione del livello [dao], l’interfaccia [IArticlesDao] per l’accesso ai dati era stata implementata tramite una classe che memorizzava gli articoli in un oggetto [ArrayList]. Ciò ci ha permesso di non soffermarci troppo su questo livello e di dimostrare che ciò che contava era solo la sua interfaccia e non la sua implementazione. Abbiamo così potuto realizzare un’applicazione web operativa. Questa presenta tre livelli: [web], [domain] e [dao]. Proporremo qui diverse implementazioni del livello [dao]. Ciascuna di esse potrà sostituire l’attuale livello [dao] senza alcuna modifica ai livelli [domain] e [web]. Questa flessibilità è garantita dal fatto che:
- il livello [domain] non si rivolge a una classe concreta ma a un'interfaccia [IArticlesDao]
- grazie a Spring, siamo riusciti a nascondere al livello [domain] il nome della classe di implementazione dell’interfaccia [IArticlesDao].
2.2.5.1. Elementi del livello [dao]
Ricordiamo alcuni degli elementi del livello [dao] che saranno mantenuti nelle nuove implementazioni:
- - [IArticlesDao]: l’interfaccia di accesso al livello [dao]
- - [Article]: classe che definisce un articolo
2.2.5.2. La classe [Article]
La classe che definisce un articolo è la seguente:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' campi privati
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' ID articolo
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
End If
Me._id = Value
End Set
End Property
' nome articolo
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("Le champ nom [" + Value + "] est invalide")
End If
Me._nom = Value
End Set
End Property
' prezzo articolo
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
End If
Me._prix = Value
End Set
End Property
' stock attuale articolo
Public Property stockactuel() As Integer
Get
Return _stockactuel
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
End If
Me._stockactuel = Value
End Set
End Property
' scorte minime articolo
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
End If
Me._stockminimum = Value
End Set
End Property
' produttore predefinito
Public Sub New()
End Sub
' produttore con proprietà
Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
Me.id = id
Me.nom = nom
Me.prix = prix
Me.stockactuel = stockactuel
Me.stockminimum = stockminimum
End Sub
' metodo di identificazione dell'articolo
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Questa classe offre:
- un costruttore che consente di impostare le 5 informazioni relative a un articolo: [id, nom, prix, stockactuel, stockminimum]
- proprietà pubbliche che consentono di leggere e scrivere le 5 informazioni.
- una verifica dei dati inseriti nell'articolo. In caso di dati errati, viene generata un'eccezione.
- un metodo toString che consente di ottenere il valore di un articolo sotto forma di stringa di caratteri. Ciò è spesso utile per il debug di un'applicazione.
2.2.5.3. L'interfaccia [IArticlesDao]
L'interfaccia [IArticlesDao] è definita come segue:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' elenco di tutti gli articoli
Function getAllArticles() As IList
' aggiunge un articolo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' elimina un articolo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modifica un articolo
Function modifieArticle(ByVal unArticle As Article) As Integer
' cerca un articolo
Function getArticleById(ByVal idArticle As Integer) As Article
' elimina tutti gli articoli
Sub clearAllArticles()
' modifica le scorte di un articolo
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
Il ruolo dei diversi metodi dell'interfaccia è il seguente:
restituisce tutti gli articoli presenti nella fonte dati | |
svuota la fonte dati | |
restituisce l'oggetto [Article] identificato dal suo numero | |
consente di aggiungere un articolo alla fonte dati | |
consente di modificare un articolo della fonte dati | |
consente di eliminare un articolo dalla fonte dati | |
consente di modificare lo stock di un articolo nella fonte dati |
L'interfaccia mette a disposizione dei programmi client una serie di metodi definiti esclusivamente dalle loro firme. Non si occupa del modo in cui tali metodi saranno effettivamente implementati. Ciò garantisce flessibilità all'applicazione. Il programma client effettua le chiamate su un'interfaccia e non su una sua specifica implementazione.
![]() |
La scelta di una specifica implementazione avviene tramite un file di configurazione Spring.
2.3. La classe di implementazione [ArticlesDaoPlainODBC]
Proponiamo una nuova implementazione del livello [dao] che presuppone che i dati si trovino in una fonte ODBC. È noto che su Windows, quasi tutti i SGBD presenti sul mercato dispongono di un driver ODBC. Il vantaggio di questa soluzione è che è possibile cambiare SGBD in modo trasparente per l’applicazione. Lo svantaggio è che un driver ODBC che sfrutta solo le caratteristiche comuni a tutti i SGBD è in genere meno performante di un driver scritto specificamente per sfruttare tutto il potenziale di un particolare SGBD. Si può consultare il paragrafo 3.7 per scoprire un esempio di creazione di un codice sorgente ODBC.
2.3.1. Il codice
2.3.1.1. Lo scheletro
La classe [ArticlesDaoPlainODBC] implementa l’interfaccia [IArticlesDao] nel modo seguente:
Commenti:
- riga 3: si importa lo spazio dei nomi contenente le classi .NET per l'accesso alle fonti ODBC
- riga 11: memorizza la connessione alla fonte ODBC
- riga 12: memorizza il nome DSN della fonte dati
- righe 13-19 - variabili private di tipo [OdbcCommand] che definiscono le query SQL utilizzate dai diversi metodi della classe
- righe 22-27 - il costruttore. Riceve gli elementi che gli consentono di costruire l'oggetto [OdbcConnection] che collegherà il codice alla fonte dati ODBC
- righe 29-31 - il metodo per l'aggiunta di un articolo
- righe 33-35 - il metodo per modificare le scorte di un articolo
- righe 37-39 - il metodo che elimina tutti gli articoli dalla fonte dati ODBC
- righe 41-43 - il metodo che recupera l'elenco di tutti gli articoli dalla fonte ODBC
- righe 45-47 - il metodo che consente di recuperare un articolo specifico
- righe 49-51 - il metodo che consente di modificare alcuni campi di un articolo di cui si conosce il numero
- righe 53-55 - il metodo che consente di eliminare un articolo di cui si conosce il numero
- righe 57-60 - metodo di utilità che consente di eseguire un [SELECT] sulla fonte di dati e di restituirne il risultato
- righe 62-64 - metodo di utilità che consente di eseguire un [INSERT, UPDATE, DELETE] sulla fonte dati e di restituirne il risultato
2.3.1.2. Il costruttore
Commenti:
- riga 2 - il costruttore riceve le tre informazioni necessarie per connettersi a una fonte ODBC: il nome DSN della fonte, l'identità con cui effettuare la connessione e la password associata.
- riga 8 - si memorizza il nome DSN della sorgente per poterlo riportare nei messaggi di errore.
- riga 9 - viene istanziato l'oggetto [OdbcConnection]. Una connessione istanziata non è una connessione aperta. È il metodo [open] che provvede all'apertura.
- righe 12-19 - si preparano le query SQL negli oggetti [OdbcCommand]. In questo modo non sarà necessario ricostruirle ogni volta che se ne avrà bisogno. I parametri formali ? delle query saranno sostituiti al momento dell'esecuzione della query con valori reali.
2.3.1.3. Il metodo executeQuery
Commenti:
- il metodo [executeQuery] è un metodo di utilità che:
- esegue una query [SELECT id, nom, prix, stockactuel, stockminimum from ARTICLES ...] sulla fonte dati
- restituisce il risultato sotto forma di un elenco di oggetti [Article]
- riga 1 - l'unico parametro del metodo è l'oggetto [OdbcCommand] contenente la query [Select] da eseguire.
- riga 7 - la connessione è aperta. Verrà chiusa alla riga 29, indipendentemente dal fatto che si verifichi o meno un errore.
- riga 9 - viene istanziato l'oggetto [OdbcDataReader] necessario per elaborare il risultato di [Select]
- righe 13-23 - ogni riga risultante da [Select] viene inserita in un oggetto [Article] che si unirà agli altri articoli in un oggetto [ArrayList]
- l'elenco degli articoli viene restituito alla riga 25
- Non viene gestita alcuna eccezione. Dovrà essere gestita dal codice che chiama questo metodo.
2.3.1.4. Il metodo executeUpdate
Commenti:
- il metodo riceve un oggetto [OdbcCommand] che contiene una richiesta SQL di tipo [Insert, Update, Delete].
- La connessione viene aperta alla riga 5. Verrà chiusa alla riga 10, indipendentemente dal fatto che si sia verificata o meno un'eccezione.
- La richiesta di aggiornamento viene eseguita alla riga 7. Il risultato, ovvero il numero di righe della tabella ARTICLES modificate dalla richiesta, viene restituito immediatamente.
2.3.1.5. Il metodo ajouteArticle
Commenti:
- riga 1 - il metodo riceve l'articolo da aggiungere alla fonte dati ODBC. Restituisce il numero di righe interessate da questa operazione, c.a.d. 1 o 0
- righe 3 e 20 - il metodo è sincronizzato. Questo vale per tutti i metodi di accesso ai dati. Ciò implica che un solo thread alla volta potrà operare sulla fonte di dati. Probabilmente si tratta di un approccio troppo conservativo. Esistono alternative migliori, in particolare quella di includere queste operazioni all’interno di transazioni. In questo caso, è il SGBD a gestire gli accessi concorrenti. Non abbiamo voluto introdurre il concetto di transazione in questa fase. Spring ci offre la possibilità di introdurle nel livello [domain]. Forse avremo occasione di tornarci sopra in un altro articolo.
- Nelle righe 5-12, vengono assegnati i valori ai parametri formali della richiesta dell’oggetto [insertCommand] inizializzato dal costruttore. Ricordiamo la richiesta:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
I 5 valori necessari per la query sono forniti dalle righe 7-11.
- Nelle righe 13-19, la richiesta viene eseguita. Se va a buon fine, viene restituito il risultato. In caso contrario, viene generata un’eccezione generica con un messaggio di errore esplicito
2.3.1.6. Il metodo modifieArticle
Commenti:
- riga 1 - il metodo riceve l’articolo da modificare dalla fonte dati ODBC. Restituisce il numero di righe interessate da questa operazione, c.a.d. 1 o 0
- i commenti del metodo [ajouteArticle] possono essere riportati qui
2.3.1.7. Il metodo supprimeArticle
Commenti:
- riga 1 - il metodo riceve il numero dell'articolo da eliminare dalla fonte dati ODBC. Restituisce il numero di righe interessate da questa operazione, c.a.d. 1 o 0
- i commenti del metodo [ajouteArticle] possono essere riportati qui
2.3.1.8. Il metodo getAllArticles
Commenti:
- riga 1 - il metodo non riceve alcun parametro. Restituisce l'elenco di tutti gli articoli della fonte dati ODBC
- la query [Select], che richiede tutti gli articoli, viene inviata al metodo [executeQuery] - riga 6
- l'elenco ottenuto viene restituito alla riga 8
- nelle righe 9-12 viene gestita un'eventuale eccezione
2.3.1.9. Il metodo getArticleById
Commenti:
- riga 1 - il metodo riceve come parametro il numero dell'articolo desiderato. Restituisce tale numero se lo trova nella fonte ODBC, altrimenti restituisce il riferimento [nothing].
- La richiesta [Select] relativa all'articolo viene inizializzata alle righe 5-8
- viene eseguita alla riga 12: si ottiene un elenco di articoli
- se l'elenco è vuoto, viene restituito il riferimento [nothing] alla riga 14
- altrimenti, l’unico articolo dell’elenco viene restituito alla riga 16
- nelle righe 17-20 viene gestita un'eventuale eccezione
2.3.1.10. Il metodo clearAllArticles
Commenti:
- riga 1 - il metodo non riceve alcun parametro e non restituisce nulla
- riga 6 - viene eseguita la richiesta di eliminazione di tutti gli articoli
- righe 7-10: viene gestita un'eventuale eccezione
2.3.1.11. Il metodo changerStockArticle
Commenti:
- riga 1 - il metodo riceve come parametri il numero dell'articolo di cui si deve modificare la giacenza e il relativo incremento (positivo o negativo). Restituisce il numero di righe modificate dall'operazione c.a.d. 0 o 1.
- righe 5-10: viene inizializzata la query [updateStockCommand]. Ricordiamo il testo della query SQL:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
si noti che lo stock viene modificato solo se, una volta modificato, rimane >=0.
- La richiesta di aggiornamento delle scorte dell’articolo viene eseguita alla riga 13 e il risultato restituito
- righe 14-18, si gestisce un'eventuale eccezione
2.3.2. Generazione dell'assembly del livello [dao]
Il progetto Visual Studio di questa nuova versione del layer [dao] presenta la seguente struttura:

Il progetto è configurato per generare un DLL denominato [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. Test NUnit del livello [dao]
2.3.3.1. Creazione di una fonte ODBC-Firebird
Per testare il nostro nuovo livello [dao] abbiamo bisogno di una fonte di dati ODBC e quindi di un database. Utilizziamo Firebird SGBD (paragrafo 3.5). Con IBExpert (paragrafo 3.6), creiamo il seguente database degli articoli:
![]() | ![]() |
L’amministratore di questo database sarà l’utente [SYSDBA] con la password [masterkey]. Creiamo alcuni articoli:

Ora creiamo la seguente sorgente Firebird ODBC (cfr. paragrafo 3.7):
![]() |
Il file sorgente ODBC creato presenta le seguenti caratteristiche:
- nome DSN: odbc-firebird-articoli
- ID di connessione: SYSDBA
- password associata: masterkey
2.3.3.2. La classe di test NUnit
Abbiamo già scritto una classe di test per il livello [dao] inizialmente creato. Se il lettore se lo ricorda, questa classe non testava una classe specifica, ma l’interfaccia [IArticlesDao]:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' l'oggetto da testare
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' si recupera un'istanza del generatore di oggetti Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' si richiede l'istanziazione dell'oggetto articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
Si nota che nel metodo dell'attributo <Setup()> si richiede a Spring un riferimento al singleton denominato [articlesdao] di tipo [IArticlesDao], ovvero del tipo dell'interfaccia. Il singleton [articlesdao] era definito dal seguente file di configurazione [spring-config.xml]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>
Dimostriamo che la classe di test iniziale ci permette di testare il nostro nuovo livello [dao] senza modifiche né ricompilazioni.
- Creiamo nella cartella Visual Studio del nostro nuovo livello [dao] la cartella [tests] (in basso a destra) copiando la cartella [bin] dal progetto di test del livello iniziale [dao] (in basso a sinistra). Se necessario, il lettore è invitato a rivedere il progetto di test della prima versione del livello [dao] nella prima parte dell’articolo.
![]() | ![]() |
- nella cartella [tests] sostituiamo il file DLL [webarticles-dao.dll] proveniente dalvecchio livello [dao] con DLL e [webarticles-dao.dll] provenienti dal nuovo livello [dao]
- Modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoPlainODBC]:
Commenti:
- riga 6, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ ArticlesDaoPlainODBC]
- questa classe ha un costruttore a tre argomenti:
- il nome della fonte DSN - riga 8
- l'identità con cui si accederà al database - riga 11
- la password associata a tale identità - riga 14
Qui riprendiamo le informazioni della sorgente ODBC-Firebird che abbiamo creato in precedenza.
2.3.3.3. Test
Ora siamo pronti per i test. Utilizzando l’applicazione [Nunit-Gui], carichiamo il file DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Osservando la schermata sopra riportata, si potrebbe rimpiangere il nome [NUnitTestArticlesDaoArrayList] assegnato inizialmente alla classe di test. Ciò crea confusione. È infatti la classe [ArticlesDaoPlainODBC] ad essere qui testata. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che avevamo inserito nella tabella [ARTICLES]. Ora eseguiamo tutti i test:

Nella finestra a sinistra è riportato l'elenco dei metodi testati. Il colore del puntino che precede il nome di ciascun metodo indica se il metodo ha superato il test (verde) o meno (rosso). Il lettore che visualizza questo documento sullo schermo potrà constatare che tutti i test sono stati superati.
2.3.3.4. Conclusione
Abbiamo appena dimostrato che:
- poiché la classe di test NUnit faceva riferimento non a una classe ma a un'interfaccia;
- perché il nome esatto della classe di istanziazione dell’interfaccia era fornito in un file di configurazione e non nel codice;
- poiché Spring si occupava di istanziare la classe e di fornirne un riferimento al codice di test;
quindi il codice di test scritto per il livello iniziale [dao] rimaneva valido per una nuova implementazione di quello stesso livello. Non abbiamo avuto bisogno di accedere al codice della classe di test. Abbiamo utilizzato solo la sua versione compilata, quella generata durante il test del livello iniziale [dao]. Trarremo conclusioni analoghe quando sarà necessario integrare il nuovo livello [dao] nell’applicazione [webarticles].
2.3.4. Integrazione del nuovo livello [dao] nell’applicazione [webarticles]
2.3.4.1. I test di integrazione
Ricordiamo che la versione iniziale dell’applicazione [webarticles] era stata distribuita nella cartella [runtime] seguente:
![]() | ![]() |
![]() |
Si invita il lettore a consultare, se necessario, il paragrafo 2.2.4 che illustra in dettaglio le modalità di distribuzione dell’applicazione [webarticles]. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- nella cartella [bin], il file DLL del vecchio livello [dao] viene sostituito dal file DLL del nuovo livello [dao]
- in [runtime], il file di configurazione [web.config] viene sostituito da un file che tiene conto della nuova classe di implementazione del livello [dao]:
![]() |
![]() |
Il nuovo file di configurazione [web.config] è il seguente:
Commenti:
- le righe 14-24 associano al singleton [articlesDao] un'istanza della nuova classe [ArticlesDaoPlainODBC]. Questa è l'unica modifica. L'abbiamo già riscontrata durante i test del nuovo livello [dao].
Siamo pronti per i test. Configuriamo il server web [Cassini] allo stesso modo descritto nel paragrafo 2.2.4. Inizializziamo la tabella degli articoli [Firebird] con i seguenti valori:

Assicurarsi che il server web Cassini e i server SGBD e [Firebird] siano in esecuzione. Con un browser, accediamo all’URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES] nel database [Firebird]:

Gli articoli [parapluie] e [bottes] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [chapeau] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.3.4.2. Conclusione
Cosa abbiamo fatto?
- abbiamo ripristinato la versione di distribuzione della versione precedente;
- abbiamo sostituito l'articolo DLL del livello [dao] con una nuova versione. I file DLL dei livelli [web] e [domain] sono rimasti invariati;
- abbiamo modificato il file di configurazione [web.config] affinché tenga conto della nuova classe di implementazione del livello [dao]
Tutto ciò garantisce un codice pulito e offre una grande flessibilità di evoluzione all’applicazione web. Queste importanti caratteristiche ci vengono fornite da due scelte architetturali:
- l’accesso ai livelli tramite interfacce
- l’integrazione e la configurazione dei livelli tramite Spring.
Proponiamo ora una nuova implementazione del livello [dao].
2.4. La classe di implementazione [ArticlesDaoSqlServer]
La seconda implementazione del livello [dao] presuppone che i dati si trovino in un database SQL Server. Microsoft mette a disposizione un SGBD denominato MSDE, che è una versione limitata di SQL Server. In allegato, al paragrafo 3.12, è indicato come ottenerlo e installarlo.
2.4.1. Il codice
La classe [ArticlesDaoSqlServer] è molto simile alla classe [ArticlesDaoPlainODBC] esaminata in precedenza. Pertanto, indicheremo solo le modifiche apportate rispetto alla versione precedente:
- le classi necessarie si trovano nello spazio dei nomi [System.Data.SqlClient] anziché nello spazio dei nomi [System.Data.Odbc]
- la connessione di tipo [OdbcConnection] ha ora il tipo [SqlConnection]
- gli oggetti [OdbcCommand] hanno ora il tipo [SqlCommand]
- la sintassi delle query SQL configurate cambia. La query di inserimento diventa quindi:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
mentre in precedenza era:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
- il metodo [ajouteArticle] diventa quindi il seguente:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di inserimento
With insertCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", unArticle.id))
.Add(New SqlParameter("@nom", unArticle.nom))
.Add(New SqlParameter("@prix", unArticle.prix))
.Add(New SqlParameter("@sa", unArticle.stockactuel))
.Add(New SqlParameter("@sm", unArticle.stockminimum))
End With
Try
'la si esegue
Return executeUpdate(insertCommand)
Catch ex As Exception
'errore nella query
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- viene modificato anche il costruttore:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: nome dell'istanza SQL server da raggiungere
' databaseName: nome del database da raggiungere
' uid: identità dell'utente
' password: la sua password
'si recupera il nome del database passato come argomento
Me.databaseName = databaseName
': si instanzia la connessione
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' si preparano le query SQL
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
End Sub
Il costruttore ora ammette quattro parametri:
' server: nome dell'istanza SQL server da raggiungere
' databaseName: nome del database da raggiungere
' uid: identificativo dell'utente
' password: la sua password
Il codice completo della classe [ArticlesDaoSqlServer] è il seguente:
Imports System
Imports System.Collections
Imports System.Data.SqlClient
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlServer
Implements istia.st.articles.dao.IArticlesDao
' campi privati
Private connexion As SqlConnection = Nothing
Private databaseName As String
Private insertCommand As SqlCommand
Private updatecommand As SqlCommand
Private deleteSomeCommand As SqlCommand
Private selectSomeCommand As SqlCommand
Private updateStockCommand As SqlCommand
Private deleteAllCommand As SqlCommand
Private selectAllCommand As SqlCommand
' costruttore
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: nome dell'istanza SQL da raggiungere
' databaseName: nome del database da raggiungere
' uid: identità dell'utente
' password: la sua password
'si recupera il nome del database passato come argomento
Me.databaseName = databaseName
': si instanzia la connessione
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' si preparano le query SQL
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
updatecommand = New SqlCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
deleteSomeCommand = New SqlCommand("delete from ARTICLES where id=@id", connexion)
selectSomeCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
updateStockCommand = New SqlCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
selectAllCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New SqlCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di inserimento
With insertCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", unArticle.id))
.Add(New SqlParameter("@nom", unArticle.nom))
.Add(New SqlParameter("@prix", unArticle.prix))
.Add(New SqlParameter("@sa", unArticle.stockactuel))
.Add(New SqlParameter("@sm", unArticle.stockminimum))
End With
Try
'si esegue
Return executeUpdate(insertCommand)
Catch ex As Exception
'errore nella query
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' sezione esclusiva
SyncLock Me
' si prepara la richiesta di aggiornamento delle scorte
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'in esecuzione
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
End Try
End SyncLock
End Function
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' sezione esclusiva
SyncLock Me
Try
'si esegue la richiesta di inserimento
executeUpdate(deleteAllCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
End Try
End SyncLock
End Sub
Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
' sezione esclusiva
SyncLock Me
Try
'si sta eseguendo la query SELECT
Dim articles As IList = executeQuery(selectAllCommand)
'viene restituito l'elenco
Return articles
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
End Try
End SyncLock
End Function
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' sezione esclusiva
SyncLock Me
' si prepara la query SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
': viene eseguita
Try
'si esegue la query
Dim articles As IList = executeQuery(selectSomeCommand)
'si verifica se l'articolo è stato trovato
If articles.Count = 0 Then Return Nothing
'si restituisce l'articolo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di aggiornamento
With updatecommand.Parameters
.Clear()
.Add(New SqlParameter("@nom", unArticle.nom))
.Add(New SqlParameter("@prix", unArticle.prix))
.Add(New SqlParameter("@sa", unArticle.stockactuel))
.Add(New SqlParameter("@sm", unArticle.stockminimum))
.Add(New SqlParameter("@id", unArticle.id))
End With
' la si esegue
Try
'si esegue la query di inserimento
Return executeUpdate(updatecommand)
Catch ex As Exception
'errore di query
Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
End Try
End SyncLock
End Function
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di cancellazione
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'si esegue
Try
'si esegue la query di cancellazione
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Private Function executeQuery(ByVal query As SqlCommand) As IList
' esecuzione di una query SELECT
' dichiarazione dell'oggetto che consente l'accesso a tutte le righe della tabella dei risultati
Dim myReader As SqlDataReader = Nothing
Try
'si crea una connessione a BDD
connexion.Open()
'si esegue la query
myReader = query.ExecuteReader()
'si dichiara un elenco di articoli per restituirlo in seguito
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'si prepara un articolo con i valori del lettore
unArticle = New Article
unArticle.id = myReader.GetInt32(0)
unArticle.nom = myReader.GetString(1)
unArticle.prix = myReader.GetDouble(2)
unArticle.stockactuel = myReader.GetInt32(3)
unArticle.stockminimum = myReader.GetInt32(4)
': si aggiunge l'articolo all'elenco
articles.Add(unArticle)
End While
'si restituisce il risultato
Return articles
Finally
' liberazione delle risorse
If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
Private Function executeUpdate(ByVal updateCommand As SqlCommand) As Integer
' esecuzione di una richiesta di aggiornamento
Try
'si crea una connessione a BDD
connexion.Open()
'si esegue la richiesta
Return updateCommand.ExecuteNonQuery()
Finally
' liberazione delle risorse
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a interpretare questo codice alla luce dei commenti relativi alla classe [ArticlesDaoPlainODBC] forniti in precedenza.
2.4.2. Generazione dell’assembly del livello [dao]
Il nuovo progetto Visual Studio presenta la seguente struttura:

Il progetto è configurato per generare un DLL denominato [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. Test NUnit del livello [dao]
2.4.3.1. Creazione di un server di dati sorgente SQL
Per testare il nostro nuovo livello [dao] abbiamo bisogno di una fonte dati SQL Server e quindi del SGBD SQL Server. Utilizzeremo infatti il SGBD MSDE (MicroSoft Data Engine) (paragrafo 3.12), che è una versione del SQL Server limitata semplicemente dal numero di utenti simultanei ammessi. Con [EMS MS SQL Manager] (paragrafo 3.14) creiamo il seguente database di articoli in un'istanza MSDE denominata [portable1_tahe\msde140405]:
![]() | ![]() |

La tabella è di proprietà dell'utente [mdparticles] con password [admarticles]. Il comando Transact-SQL per la creazione della tabella [ARTICLES] è il seguente:
CREATE TABLE [ARTICLES] (
[id] int NOT NULL,
[nom] varchar(20) COLLATE French_CI_AS NOT NULL,
[prix] float(53) NOT NULL,
[stockactuel] int NOT NULL,
[stockminimum] int NOT NULL,
CONSTRAINT [ARTICLES_uq] UNIQUE ([nom]),
PRIMARY KEY ([id]),
CONSTRAINT [ARTICLES_ck_id] CHECK ([id] > 0),
CONSTRAINT [ARTICLES_ck_nom] CHECK ([nom] <> ''),
CONSTRAINT [ARTICLES_ck_prix] CHECK ([prix] >= 0),
CONSTRAINT [ARTICLES_ck_stockactuel] CHECK ([stockactuel] >= 0),
CONSTRAINT [ARTICLES_ck_stockminimum] CHECK ([stockminimum] >= 0)
)
ON [PRIMARY]
GO
Creiamo alcuni articoli:

2.4.3.2. La classe di test NUnit
La classe di test NUnit della classe di implementazione [ArticlesDaoSqlServer] è la stessa di quella della classe [ArticlesDaoPlainODBC] (cfr. paragrafo 2.3.3.2). Seguiamo una procedura analoga per preparare il test NUnit della classe:
- creiamo nella cartella Visual Studio del progetto [dao-sqlserver] la cartella [tests] (a destra) copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] del progetto [dao-sqlserver], sostituiamo DLL [webarticles-dao.dll] con DLL [webarticles-dao.dll] generati dal progetto [dao-sqlserver]
- modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoSqlServer]:
Commenti:
- alla riga 7, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ ArticlesDaoSqlServeur]
- questa classe ha un costruttore a quattro argomenti:
- il nome dell'istanza MSDE utilizzata - riga 9
- il nome del database - riga 12
- l'identità con cui si accederà al database - riga 15
- la password associata a tale identità - riga 18
Qui riprendiamo le informazioni della sorgente MSDE che abbiamo creato in precedenza.
2.4.3.3. Test
Siamo pronti per i test. Utilizzando l’applicazione [Nunit-Gui], carichiamo il file DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Nonostante il nome [NUnitTestArticlesDaoArrayList] assegnato inizialmente alla classe di test e che è stato mantenuto poiché utilizziamo i file DLL e [tests-webarticles-dao.dll] derivati da questa classe, è proprio la classe [ArticlesDaoSqlserver] quella qui sottoposta a test. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che avevamo inserito nella tabella [ARTICLES]. Ora eseguiamo tutti i test:

Nella finestra a sinistra è visibile l’elenco dei metodi testati. Il colore del punto che precede il nome di ciascun metodo indica il superamento (verde) o il fallimento (rosso) del metodo. Il lettore che visualizza questo documento sullo schermo potrà constatare che tutti i test sono stati superati.
2.4.4. Integrazione del nuovo livello [dao] nell’applicazione [webarticles]
Seguiamo la procedura illustrata al paragrafo 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- nella cartella [bin], il file DLL delvecchio livello [dao] viene sostituito dal DLL del nuovo livello [dao] implementato dalla classe [ArticlesDaoSqlServer]
- in [runtime], il file di configurazione [web.config] viene sostituito da un file che tiene conto della nuova classe di implementazione:
Commenti:
- le righe 15-33 associano al singleton [articlesDao] un'istanza della nuova classe [ArticlesDaoSqlServer]. Questa è l'unica modifica. L'abbiamo già riscontrata durante i test del nuovo livello [dao]
Siamo pronti per i test. Manteniamo la stessa configurazione del server web [Cassini] di prima. Inizializziamo la tabella degli articoli [MSDE] con i seguenti valori:

Assicurarsi che il server web Cassini e SGBD MSDE (in questo caso l’istanza portable1_tahe\msde140405) siano in esecuzione. Con un browser, accediamo a URL e [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES] nel database [MSDE]:

Gli articoli [ballon foot] e [raquette tennis] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [rollers] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.4.5. La classe di implementazione [ArticlesDaoOleDb]
2.4.5.1. Gli i di origine dati OleDb
La terza implementazione del livello [dao] presuppone che i dati si trovino in un database accessibile tramite un driver OleDb. Il principio delle fonti OleDb è analogo a quello delle fonti ODBC. Un programma che utilizza una sorgente OleDb lo fa tramite un'interfaccia standard comune a tutte le sorgenti OleDb. Cambiare la sorgente OleDb equivale semplicemente a cambiare il driver OleDb. Il codice non viene modificato.
È possibile verificare quali driver OleDb sono disponibili sul proprio computer tramite Visual Studio:
- visualizzare l’esploratore dei server tramite [Affichage/Explorateur de serveurs]:

- per aggiungere una nuova connessione, fare clic con il tasto destro su [Connexion de données] e selezionare l’opzione [Ajouter une connexion]. Si aprirà quindi una procedura guidata con cui è possibile definire le caratteristiche della connessione:

- Il pannello [Fournisseur] mostra l'elenco dei driver OLEDB disponibili. Per il nuovo livello [dao] utilizzeremo un driver [Microsoft Jet 4.0 OLE DB Provider] che consente l’accesso ai database ACCESS.
- Usciamo momentaneamente da Visual Studio per creare la base ACCESS [articles.mdb] contenente la seguente tabella:

- La struttura della tabella è la seguente:
numerico - intero - chiave primaria | |
testo - 20 caratteri - | |
numerico - reale doppio | |
numerico - intero | |
numerico - intero |
- Torniamo a Visual Studio e creiamo una nuova connessione come spiegato in precedenza:

- Scegliamo il driver [Microsoft Jet 4.0] e passiamo al pannello [Connexion]:

- con il pulsante [1], selezioniamo il database ACCESS appena creato, quindi completiamo la definizione della connessione con il pulsante [Terminer]. La connessione creata appare ora nell’elenco delle connessioni disponibili:

- un doppio clic sulla tabella [ARTICLES] ci consente di accedere al suo contenuto:

- È quindi possibile aggiungere, modificare o eliminare righe nella tabella.
- Selezionare nell’Explorer dei server la nuova connessione per accedere alla sua scheda delle proprietà:

- È utile conoscere la stringa di connessione. Ci servirà per collegarci al database:
Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\data\serge\databases\access\articles\articles.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False
- Di questa stringa, terremo a mente solo i seguenti elementi:
2.4.5.2. Il codice della classe [ArticlesDaoOleDb]
La classe [ArticlesDaoOleDb] è molto simile alla classe [ArticlesDaoPlainODBC] esaminata in precedenza. Indicheremo quindi solo le modifiche apportate rispetto alla versione precedente:
- le classi necessarie si trovano nello spazio dei nomi [System.Data.OleDb] anziché nello spazio dei nomi [System.Data.Odbc]
- la connessione di tipo [OdbcConnection] ha ora il tipo [OleDbConnection]
- gli oggetti [OdbcCommand] hanno ora il tipo [OleDbCommand]
Il costruttore della classe accetta come unico parametro la stringa di connessione al database:
' costruttore
Public Sub New(ByVal connectString As String)
' connectString: stringa di connessione alla fonte OleDb
'si istanzia la connessione
connexion = New OleDbConnection(connectString)
' si preparano le richieste SQL
...
End Sub
Il codice completo della classe [ArticlesDaoOleDb] è il seguente:
Imports System
Imports System.Collections
Imports System.Data.OleDb
Namespace istia.st.articles.dao
Public Class ArticlesDaoOleDb
Implements istia.st.articles.dao.IArticlesDao
' campi privati
Private connexion As OleDbConnection = Nothing
Private insertCommand As OleDbCommand
Private updatecommand As OleDbCommand
Private deleteSomeCommand As OleDbCommand
Private selectSomeCommand As OleDbCommand
Private updateStockCommand As OleDbCommand
Private deleteAllCommand As OleDbCommand
Private selectAllCommand As OleDbCommand
' costruttore
Public Sub New(ByVal connectString As String)
' connectString: stringa di connessione alla fonte OleDb
'si istanzia la connessione
connexion = New OleDbConnection(connectString)
' si preparano le richieste SQL
insertCommand = New OleDbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
updatecommand = New OleDbCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
deleteSomeCommand = New OleDbCommand("delete from ARTICLES where id=?", connexion)
selectSomeCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
updateStockCommand = New OleDbCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
selectAllCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New OleDbCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' sezione esclusiva
SyncLock Me
' si prepara la richiesta di inserimento
With insertCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", unArticle.id))
.Add(New OleDbParameter("nom", unArticle.nom))
.Add(New OleDbParameter("prix", unArticle.prix))
.Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
.Add(New OleDbParameter("stockminimum", unArticle.stockminimum))
End With
Try
'si esegue la richiesta
Return executeUpdate(insertCommand)
Catch ex As Exception
'errore nella query
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di aggiornamento delle scorte
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'in esecuzione
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
End Try
End SyncLock
End Function
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' sezione esclusiva
SyncLock Me
Try
'si esegue la richiesta di inserimento
executeUpdate(deleteAllCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
End Try
End SyncLock
End Sub
Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
' sezione esclusiva
SyncLock Me
Try
'si sta eseguendo la query SELECT
Dim articles As IList = executeQuery(selectAllCommand)
': viene restituito l'elenco
Return articles
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
End Try
End SyncLock
End Function
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' sezione esclusiva
SyncLock Me
' si prepara la query SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'la si esegue
Try
': si esegue la query
Dim articles As IList = executeQuery(selectSomeCommand)
'si verifica se l'articolo è stato trovato
If articles.Count = 0 Then Return Nothing
'si restituisce l'articolo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'errore nella richiesta
Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di aggiornamento
With updatecommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", unArticle.nom))
.Add(New OleDbParameter("prix", unArticle.prix))
.Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
.Add(New OleDbParameter("stockminimum", unArticle.stockactuel))
.Add(New OleDbParameter("id", unArticle.id))
End With
' la si esegue
Try
'si esegue la query di inserimento
Return executeUpdate(updatecommand)
Catch ex As Exception
'errore di query
Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
End Try
End SyncLock
End Function
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di cancellazione
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'si esegue
Try
'si esegue la query di cancellazione
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Private Function executeQuery(ByVal query As OleDbCommand) As IList
' esecuzione di una query SELECT
' dichiarazione dell'oggetto che consente l'accesso a tutte le righe della tabella dei risultati
Dim myReader As OleDbDataReader = Nothing
Try
'si crea una connessione a BDD
connexion.Open()
'si esegue la query
myReader = query.ExecuteReader()
'si dichiara un elenco di articoli per restituirlo in seguito
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'si prepara un articolo con i valori del lettore
unArticle = New Article
unArticle.id = myReader.GetInt32(0)
unArticle.nom = myReader.GetString(1)
unArticle.prix = myReader.GetDouble(2)
unArticle.stockactuel = myReader.GetInt32(3)
unArticle.stockminimum = myReader.GetInt32(4)
'si aggiunge l'articolo all'elenco
articles.Add(unArticle)
End While
'si restituisce il risultato
Return articles
Finally
' liberazione delle risorse
If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
Private Function executeUpdate(ByVal sqlCommand As OleDbCommand) As Integer
' esecuzione di una richiesta di aggiornamento
Try
'si crea una connessione a BDD
connexion.Open()
'si esegue la richiesta
Return sqlCommand.ExecuteNonQuery()
Finally
' liberazione delle risorse
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a interpretare questo codice alla luce dei commenti relativi alla classe [ArticlesDaoPlainODBC] forniti in precedenza.
2.4.5.3. Generazione dell'assembly del livello [dao]
Il nuovo progetto Visual Studio presenta la seguente struttura:

Il progetto è configurato per generare un DLL denominato [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. Test NUnit del livello [dao]
2.4.5.4.1. La classe di test NUnit
La classe di test NUnit della classe di implementazione [ArticlesDaoOleDb] è la stessa di quella della classe [ArticlesDaoPlainODBC] (cfr. paragrafo 2.3.3.2). Seguiamo una procedura analoga per preparare il test NUnit della classe:
- creiamo nella cartella Visual Studio del progetto [dao-oledb] la cartella [tests] (a destra) copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] del progetto [dao-oledb] sostituiamo DLL [webarticles-dao.dll] con DLL [webarticles-dao.dll] derivante dalla generazione del progetto [dao-oledb]
- modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoOleDb]:
Commenti:
- riga 7, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ ArticlesDaoOleDb]
- questa classe ha un costruttore con un argomento: la stringa di connessione al database OleDb ACCESS - riga 9
2.4.5.4.2. Tests
Siamo pronti per i test. Utilizzando l’applicazione [Nunit-Gui], carichiamo il file DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Nonostante il nome [NUnitTestArticlesDaoArrayList] assegnato inizialmente alla classe di test, è proprio la classe [ArticlesDaoOleDb] ad essere qui testata. La schermata mostra che abbiamo recuperato correttamente gli articoli che avevamo inserito nella tabella [ARTICLES]. Ora eseguiamo tutti i test:

Il lettore che visualizza questo documento sullo schermo potrà vedere che tutti i test sono stati superati (colore verde).
2.4.5.5. Integrazione del nuovo livello [dao] nell’applicazione [webarticles]
Seguiamo la procedura illustrata al paragrafo 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- nella cartella [bin], il file DLL delvecchio livello [dao] viene sostituito dal file DLL del nuovo livello [dao] implementato dalla classe [ArticlesDaoOleDb]
- in [runtime], il file di configurazione [web.config] viene sostituito da un file che tiene conto della nuova classe di implementazione:
Commenti:
- le righe 14-18 associano al singleton [articlesDao] un'istanza della nuova classe [ArticlesDaoOleDb]. Questa è l'unica modifica.
Manteniamo la stessa configurazione del server web [Cassini] di prima. Inizializziamo la tabella degli articoli con i seguenti valori:

Assicurarsi che il database degli articoli non sia in uso da parte di un programma come Visual Studio o ACCESS. Tramite un browser richiediamo l'URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES] con ACCESS:

Gli articoli [pantalon] e [jupe] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [manteau] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.5. La classe di implementazione [ArticlesDaoFirebirdProvider]
2.5.1. Il provider di accesso Firebird-net-provider
Abbiamo già utilizzato una fonte di dati [Firebird] tramite un driver ODBC. Sebbene garantiscano una grande riutilizzabilità del codice che li impiega, i driver ODBC sono tuttavia meno performanti rispetto ai driver scritti specificamente per il SGBD di destinazione. Il SGBD [Firebird] può essere utilizzato tramite una libreria di classi specifiche scaricabile dal sito di Firebird [http://firebird.sourceforge.net/]. La pagina dei download offre i seguenti link (aprile 2005):
![]()
Il link [firebird-net-provider] è quello da utilizzare per scaricare le classi .NET di accesso a SGBD Firebird. L'installazione del pacchetto crea una cartella simile alla seguente:

Ci interessano due elementi:
- [FirebirdSql.Data.Firebird.dll]: l'assembly contenente le classi .NET per l'accesso a SGBD Firebird
- [FirebirdNETProviderSDK.chm]: la documentazione relativa a queste classi
Successivamente, affinché un progetto Visual Studio possa utilizzare queste classi, faremo due cose:
- inseriremo l’assembly [FirebirdSql.Data.Firebird.dll] nella cartella [bin] del progetto
- aggiungeremo lo stesso assembly ai riferimenti del progetto
2.5.2. Il codice della classe [ArticlesDaoFirebirdProvider]
La classe [ArticlesDaoFirebirdProvider] è molto simile alla classe [ArticlesDaoSqlServer] esaminata in precedenza. Indicheremo quindi solo le modifiche apportate rispetto a quella versione:
- le classi necessarie si trovano nello spazio dei nomi [FirebirdSql.Data.Firebird] anziché nello spazio dei nomi [System.Data.SqlClient]
- la connessione di tipo [SqlConnection] ha ora il tipo [FbConnection]
- gli oggetti [SqlCommand] hanno ora il tipo [FbCommand]
- gli oggetti [SqlParameter] hanno ora il tipo [FbParameter]
Il costruttore della classe accetta quattro parametri, con i quali costruisce la stringa di connessione al database:
' costruttore
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: nome del computer host di SGBD
' databaseName: percorso di accesso al database
' uid: identificativo dell'utente che effettua l'accesso
' password: la sua password
...
End Sub
Il codice completo della classe [ArticlesDaoFirebirdProvider] è il seguente:
Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird
Namespace istia.st.articles.dao
Public Class ArticlesDaoFirebirdProvider
Implements istia.st.articles.dao.IArticlesDao
' campi privati
Private connexion As FbConnection = Nothing
Private databasePath As String
Private insertCommand As FbCommand
Private updatecommand As FbCommand
Private deleteSomeCommand As FbCommand
Private selectSomeCommand As FbCommand
Private updateStockCommand As FbCommand
Private deleteAllCommand As FbCommand
Private selectAllCommand As FbCommand
' produttore
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' server: nome del computer host di SGBD Firebird
' databaseName: percorso di accesso al database da utilizzare
' uid: identificativo dell'utente che si connette al database
' password: la sua password
'si recupera il nome del database passato come argomento
Me.databasePath = databasePath
'si instanzia la connessione
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' si preparano le query SQL
insertCommand = New FbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
updatecommand = New FbCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
deleteSomeCommand = New FbCommand("delete from ARTICLES where id=@id", connexion)
selectSomeCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
updateStockCommand = New FbCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
selectAllCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New FbCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di inserimento
With insertCommand.Parameters
.Clear()
.Add(New FbParameter("@id", unArticle.id))
.Add(New FbParameter("@nom", unArticle.nom))
.Add(New FbParameter("@prix", unArticle.prix))
.Add(New FbParameter("@sa", unArticle.stockactuel))
.Add(New FbParameter("@sm", unArticle.stockminimum))
End With
Try
'si esegue
Return executeUpdate(insertCommand)
Catch ex As Exception
'errore nella query
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' sezione esclusiva
SyncLock Me
' si prepara la richiesta di aggiornamento delle scorte
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'in esecuzione
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
End Try
End SyncLock
End Function
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' sezione esclusiva
SyncLock Me
Try
'si esegue la richiesta di inserimento
executeUpdate(deleteAllCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
End Try
End SyncLock
End Sub
Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
' sezione esclusiva
SyncLock Me
Try
'si sta eseguendo la query SELECT
Dim articles As IList = executeQuery(selectAllCommand)
'viene restituito l'elenco
Return articles
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
End Try
End SyncLock
End Function
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' sezione esclusiva
SyncLock Me
' si prepara la query SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
': viene eseguita
Try
'si esegue la query
Dim articles As IList = executeQuery(selectSomeCommand)
'si verifica se l'articolo è stato trovato
If articles.Count = 0 Then Return Nothing
'si restituisce l'articolo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di aggiornamento
With updatecommand.Parameters
.Clear()
.Add(New FbParameter("@nom", unArticle.nom))
.Add(New FbParameter("@prix", unArticle.prix))
.Add(New FbParameter("@sa", unArticle.stockactuel))
.Add(New FbParameter("@sm", unArticle.stockminimum))
.Add(New FbParameter("@id", unArticle.id))
End With
' la si esegue
Try
'si esegue la query di inserimento
Return executeUpdate(updatecommand)
Catch ex As Exception
'errore di query
Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
End Try
End SyncLock
End Function
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' sezione esclusiva
SyncLock Me
' si prepara la query di cancellazione
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'si esegue
Try
'si esegue la query di cancellazione
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'errore di query
Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
End Try
End SyncLock
End Function
Private Function executeQuery(ByVal query As FbCommand) As IList
' esecuzione di una query SELECT
' dichiarazione dell'oggetto che consente l'accesso a tutte le righe della tabella dei risultati
Dim myReader As FbDataReader = Nothing
Try
'si crea una connessione a BDD
connexion.Open()
'si esegue la query
myReader = query.ExecuteReader()
'si dichiara un elenco di articoli per restituirlo in seguito
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'si prepara un articolo con i valori del lettore
unArticle = New Article
unArticle.id = myReader.GetInt32(0)
unArticle.nom = myReader.GetString(1)
unArticle.prix = myReader.GetDouble(2)
unArticle.stockactuel = myReader.GetInt32(3)
unArticle.stockminimum = myReader.GetInt32(4)
'si aggiunge l'articolo all'elenco
articles.Add(unArticle)
End While
'si restituisce il risultato
Return articles
Finally
' liberazione delle risorse
If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
Private Function executeUpdate(ByVal updateCommand As FbCommand) As Integer
' esecuzione di una richiesta di aggiornamento
Try
': viene stabilita una connessione a BDD
connexion.Open()
'si esegue la richiesta
Return updateCommand.ExecuteNonQuery()
Finally
' liberazione delle risorse
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a interpretare questo codice alla luce dei commenti relativi alla classe [ArticlesDaoSqlServer] forniti in precedenza.
2.5.3. Generazione dell’assembly del livello [dao]
Il nuovo progetto Visual Studio presenta la seguente struttura:

Si noti la presenza dell’assembly [FirebirdSql.Data.Firebird.dll] nei riferimenti del progetto. Questo DLL è stato inserito nella cartella [bin] del progetto. Il progetto è configurato per generare un DLL denominato [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. Test Nunit del livello [dao]
2.5.4.1. La classe di test NUnit
La classe di test Nunit della classe di implementazione [ArticlesDaoFirebirdProvider] è la stessa di quella della classe [ArticlesDaoPlainODBC] (cfr. paragrafo 2.3.3.2). Seguiamo una procedura analoga per preparare il test Nunit della classe [ArticlesDaoFirebirdProvider]:
- nella cartella Visual Studio del progetto [dao-firebird-provider] creiamo la cartella [tests] (a destra) copiando la cartella [bin] dal progetto di test del livello [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] sostituiamo DLL e [webarticles-dao.dll] con DLL e [webarticles-dao.dll] generati dal progetto [dao-firebird-provider]
- modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoFirebirdProvider]:
Commenti:
- alla riga 7, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ArticlesDaoFirebirdProvider]
- questa classe ha un costruttore a quattro argomenti
- il computer host di SGBD - riga 9
- il percorso di accesso al database Firebird - riga 12
- il nome utente di chi effettua l'accesso - riga 15
- la sua password - riga 18
2.5.4.2. Test
La tabella [ARTICLES] della fonte dati è popolata con i seguenti articoli (utilizzare IBExpert):

Siamo pronti per i test. Utilizzando l'applicazione [Nunit-Gui], carichiamo il file DLL e [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Nonostante il nome [NUnitTestArticlesDaoArrayList] assegnato inizialmente alla classe di test, è proprio la classe [ArticlesDaoFirebirdProvider] ad essere qui testata. La schermata mostra che abbiamo recuperato correttamente gli articoli che avevamo inserito nella tabella [ARTICLES]. Ora eseguiamo tutti i test:

Il lettore che visualizza questo documento sullo schermo potrà vedere che tutti i test hanno avuto esito positivo (colore verde). Ciò che non può vedere è che i test si sono svolti in modo nettamente più rapido rispetto a quando si accedeva al database degli articoli tramite un driver ODBC della nostra prima implementazione.
2.5.5. Integrazione del nuovo livello [dao] nell’applicazione [webarticles]
Seguiamo la procedura già spiegata due volte, in particolare al paragrafo 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- nella cartella [bin], il file DLL delvecchio livello [dao] viene sostituito dal DLL del nuovo livello [dao] implementato dalla classe [ArticlesDaoFirebirdProvider]. Inseriamo inoltre la DLL necessaria per Firebird [FirebirdSql.Data.Firebird.dll]:

- in [runtime], il file di configurazione [web.config] viene sostituito da un file che tiene conto della nuova classe di implementazione:
Commenti:
- le righe 14-27 associano al singleton [articlesDao] un'istanza della nuova classe [ArticlesDaoFirebirdProvider]. Questa è l'unica modifica.
Siamo pronti per i test. Configuriamo il server web [Cassini] come nei test precedenti. Inizializziamo la tabella degli articoli con i seguenti valori:

Con un browser richiediamo l’URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES]:

Gli articoli [crayon bille] e [ramette 50 feuilles] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [stylo plume] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.5.6. La classe di implementazione [ArticlesDaoSqlMap]
2.5.6.1. Il prodotto Ibatis SqlMap
Abbiamo scritto quattro diverse implementazioni del livello [dao] della nostra applicazione [webarticles]. In ogni caso siamo riusciti a integrare il nuovo livello [dao] nell’applicazione [webarticles] senza ricompilare gli altri due livelli [web] e [domain]. Ciò è stato ottenuto, ricordiamo, grazie a due scelte architetturali:
- l’accesso ai livelli tramite interfacce
- l’integrazione dei livelli tramite Spring
Vorremmo spingerci un po’ oltre. Sebbene diverse, le nostre quattro implementazioni del livello [dao] presentano somiglianze sorprendenti. Una volta scritta la prima implementazione, le altre tre sono state ottenute praticamente tramite copia-incolla e sostituzione di alcune parole chiave con altre. La logica, invece, non è stata modificata. Ci si può chiedere se non sia possibile avere un’implementazione che ci liberi dalle diverse modalità di accesso ai dati. Ne abbiamo utilizzate quattro:
- accesso tramite un driver ODBC a una fonte di dati ODBC
- accesso diretto a un database SQL Server
- accesso tramite un driver Ole Db a una fonte dati Ole Db
- accesso diretto a un database Firebird
Lo strumento Ibatis SqlMap [[http://www.ibatis.com/] consente lo sviluppo di livelli di accesso ai dati indipendenti dalla natura effettiva della fonte di dati. L’accesso ai dati è garantito tramite:
- file di configurazione in cui sono inserite le informazioni che definiscono la fonte di dati e le operazioni che si desidera eseguire su di essa
- una libreria di classi che si basano su queste informazioni per accedere ai dati
Lo strumento Ibatis SqlMap è stato inizialmente sviluppato per la piattaforma Java. Il suo porting sulla piattaforma .NET è recente e, a quanto pare, presenta alcuni bug (opinione personale che richiederebbe una verifica approfondita). Tuttavia, dato che lo strumento ha dato prova di efficacia sulla piattaforma Java, sembra interessante presentarne la versione .NET.
2.5.6.2. Dove trovare IBATIS SqlMap?
Il sito principale di Firebird è [http://www.ibatis.com/]. La pagina dei download offre i seguenti link:

Selezioniamo il link [Stable Binaries] che ci reindirizza a [SourceForge.net]. Seguiamo la procedura di download fino alla fine. Otterremo un file zip contenente i seguenti file:

In un progetto Visual Studio che utilizza Ibatis SqlMap, è necessario eseguire due operazioni:
- inserire i file sopra indicati nella cartella [bin] del progetto
- aggiungere al progetto un riferimento a ciascuno di questi file
2.5.6.3. I file di configurazione di Ibatis SqlMap
Una fonte di dati [SqlMap] verrà definita tramite i seguenti file di configurazione:
- providers.config: definisce le librerie di classi da utilizzare per accedere ai dati
- sqlmap.config: definisce le caratteristiche della connessione da stabilire
- file di mappatura: definiscono le operazioni da eseguire sui dati
La logica di questi file è la seguente:
- per accedere ai dati, avremo bisogno di una connessione. Per rappresentarla, abbiamo già incontrato diverse classi: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Ci servirà inoltre un oggetto [Command] per inviare le richieste SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. E così via. Nel file [providers.config] definiamo l'insieme delle classi di cui abbiamo bisogno.
- Il file [sqlmap.config] definisce essenzialmente la stringa di connessione al database che contiene i dati. La connessione al database verrà aperta tramite l'istanziazione della classe [Connection] definita in [providers.config], al cui costruttore verrà passata la stringa di connessione definita in [sqlmap.config].
- I file di mappatura definiscono:
- le associazioni tra le righe delle tabelle di dati e la classe .NET, le cui istanze conterranno tali righe
- le operazioni SQL da eseguire. Queste sono identificate da un nome. Il codice .NET esegue queste operazioni tramite il loro nome, il che comporta l’eliminazione di tutto il codice SQL dal codice .NET.
2.5.6.4. I file di configurazione del progetto [dao-sqlmap]
Esaminiamo, con un esempio, la natura esatta dei file di configurazione di SqlMap. Consideriamo il caso in cui la fonte dei dati sia la fonte Firebird ODBC del paragrafo 2.3.3.1.
2.5.6.4.1. providers.config
Il file [providers.config] relativo alla fonte ODBC è il seguente:
Note:
- un file [providers.config] viene distribuito insieme al pacchetto [SqlMap]. Esso propone diversi provider standard. Il codice sopra riportato proviene direttamente da questo file.
- un <provider> ha un nome - riga 6 - che può essere qualsiasi
- un <provider> può essere attivato ([enabled=true]) o meno ([enabled=false]). Se è attivato, il DLL a cui si fa riferimento alla riga 8 deve essere accessibile. Un file [providers.config] può contenere più tag <provider>.
- riga 8 - nome dell'assembly che contiene le classi definite righe 9-15
- riga 9 - classe da utilizzare per creare una connessione
- riga 10 - classe da utilizzare per creare un oggetto [Command] per l’invio di comandi SQL
- riga 11 - classe da utilizzare per gestire i parametri di un comando SQL configurato
- riga 12 - classe di enumerazione dei possibili tipi di dati per i campi di una tabella
- riga 13 - nome della proprietà di un oggetto [Parameter] che contiene il tipo del valore di tale parametro
- riga 14 - nome della classe [Adapter] che consente di creare oggetti [DataSet] a partire dalla fonte dati
- riga 15 - nome della classe [CommandBuilder] che, associata a un oggetto [Adapter], consente di generare automaticamente le proprietà [InsertCommand, DeleteCommand, UpdateCommand] di quest'ultimo a partire dalla sua proprietà [SelectCommand]
- righe 16 - 19 - si definisce come vengono gestiti i comandi SQL configurati. A seconda dei casi, occorre scrivere ad esempio:
oppure
Nel primo caso si tratta di parametri posizionali formali. I valori effettivi di questi devono essere forniti nell'ordine dei parametri formali. Nel secondo caso si tratta di parametri denominati. Si assegna un valore a un parametro di questo tipo specificandone il nome. L'ordine non ha più importanza.
- riga 16 - si indica che le sorgenti ODBC utilizzano parametri posizionali
- righe 17-19 - riguardano i parametri denominati. In questo caso non ce ne sono.
Queste informazioni consentono a SqlMap di sapere, ad esempio, quale classe deve istanziare per creare una connessione. In questo caso sarà la classe [OdbcConnection] (riga 9).
2.5.6.4.2. sqlmap.config
Il file [providers.config] definisce le classi da utilizzare per accedere a una fonte ODBC. Non indica alcuna fonte ODBC. È il file [sqlmap.config] a farlo:
Commenti:
- riga 3 - si definisce un file di proprietà [properties.xml]. Questo definisce coppie (chiave, valore). Le chiavi possono essere qualsiasi cosa. Il valore associato a una chiave C si ottiene tramite la notazione ${C} nel file [sqlmap.config]. Ecco il file [properties.xml] che sarà associato al precedente file [sqlmap.config]:
riga 3 - viene definita la chiave [provider]. Il suo valore è il nome del tag <provider> da utilizzare in [providers.config]
riga 4 - è definita la chiave [connectionString]. Il suo valore è la stringa di connessione da utilizzare per aprire una connessione alla fonte dati ODBC Firebird.
- righe 4-7 - parametri di configurazione:
- riga 5 - le query SQL saranno identificate da un nome che a sua volta può far parte di uno spazio dei nomi. [useStatementNamespaces="false"] indica che non verranno utilizzati spazi dei nomi.
- riga 6 - SqlMap dispone di diverse strategie di cache per ridurre al minimo gli accessi alla fonte di dati. [cacheModelsEnabled="false"] indica che non ne verrà utilizzata alcuna.
- righe 9-13 - si definiscono le caratteristiche della fonte dati:
- riga 10 - nome del <provider> di [providers.config] da utilizzare
- riga 11 - stringa di connessione alla fonte dati
- riga 12 - gestore delle transazioni. In questo caso non lo abbiamo utilizzato, ma abbiamo comunque lasciato la riga poiché era presente nel file di distribuzione standard.
- righe 14-16 - elenco dei file che definiscono le operazioni SQL da eseguire sulla fonte dati.
- riga 15 - definisce il file di mappatura [articles.xml]
2.5.6.4.3. articles.xml
Questo file svolge due funzioni:
- definire una mappatura degli oggetti delle tabelle della fonte dati. Nei casi più semplici, ciò equivale ad associare una classe a una riga di una tabella.
- definire operazioni SQL configurate e assegnare loro un nome.
Utilizzeremo il seguente file [articles.xml]:
Commenti:
- righe 4-11: si definisce una mappatura tra una riga della tabella [ARTICLES] della fonte dati e la classe [istia.st.articles.dao.Article]. A ogni colonna (column) della tabella è associata una proprietà (property) della classe [Article]. Questa mappatura consente a [SqlMap] di costruire il risultato di un'operazione SQL SELECT. Ogni riga risultante da SELECT verrà inserita in un oggetto [Article] secondo le regole della mappatura.
- riga 5 - il mapping è racchiuso in un tag <resultMap> ed è denominato tramite l'attributo [id="article"]. La classe associata è indicata dall'attributo [class="istia.st.articles.dao.Article"].
- righe 14-44 - si definiscono le operazioni SQL necessarie
- righe 16-18 - si definisce un'operazione SELECT denominata [getAllArticles]
- riga 16 - l’operazione SELECT viene denominata [name= "getAllArticles "] e il mapping da utilizzare è definito dall’attributo [resultMap="article"]. Si fa quindi riferimento al mapping delle righe 5-11
- riga 17 - testo del comando SQL da eseguire
- righe 20-22 - si definisce il comando SQL-Delete [clearAllArticles] destinato a svuotare la tabella degli articoli.
- righe 24-27 - si definisce il comando SQL-Insert [insertArticle] destinato ad aggiungere un nuovo articolo nella tabella degli articoli. Si tratta di una query parametrizzata dagli elementi (#id#, #nome#, #prezzo#, #stockattuale#, #stockminimo#). I valori di questi cinque elementi proverranno da un oggetto [Article] passato come parametro: [parameterClass="istia.st.articles.dao.Article"]. L’oggetto parametro deve avere le proprietà (id, nome, prezzo, stockattuale, stockminimo) a cui fa riferimento il comando SQL configurato.
- righe 29-31 - si definisce il comando SQL Delete [deleteArticle] destinato a eliminare un articolo di cui si conosce il numero #value#. Questo numero verrà passato come parametro: [parameterClass="int"]. Si tratta di una regola generale. Quando il parametro è unico, viene indicato dalla parola chiave #value# nel testo del comando SQL.
- righe 33-35 - si definisce il comando SQL-Update [modifyArticle] destinato a modificare un articolo di cui si conosce il numero. Come per il comando [insertArticle], le cinque informazioni necessarie proverranno dalle proprietà di un oggetto [istia.st.articles.dao.Article].
- righe 37-39 - si definisce il comando SQL-Select [getArticleById] che consente di ottenere la riga di un articolo di cui si conosce il numero.
- righe 41-43 - si definisce il comando SQL-Update [changerStockArticle] che modifica il campo [stockactuel] di un articolo di cui si conosce il numero. Le due informazioni necessarie, il n. #id# dell’articolo e l’incremento #mouvement# delle scorte, si trovano in un dizionario: [parameterClass="Hashtable"]. Questo dovrà avere due chiavi: id e mouvement. Saranno i valori associati a queste due chiavi ad essere utilizzati nel comando SQL.
2.5.6.4.4. Posizione dei file di configurazione
Esamineremo due situazioni diverse:
- nel caso di un test Nunit, i file di configurazione di [SqlMap] saranno collocati nella stessa cartella dei binari sottoposti a test.
- nel caso di un’applicazione web, saranno collocati nella directory principale dell’applicazione.
2.5.6.5. Il API di SqlMap
Le classi di SqlMap sono contenute in DLL, che in genere viene collocato nella cartella [bin] dell’applicazione:

Le applicazioni che utilizzano le classi di SqlMap devono importare lo spazio dei nomi [IBatisNet.DataMapper]:
Tutte le operazioni SQL vengono eseguite tramite un singleton di tipo [Mapper], una classe dello spazio dei nomi [IBatisNet.DataMapper ]. Il singleton viene ottenuto nel modo seguente:
Per eseguire il comando SqlMap [getAllArticles], si scriverà:
- il metodo [QueryForList] consente di ottenere il risultato di un comando SELECT in un elenco
- il primo parametro è il nome del comando SQL da eseguire (cfr. articles.xml)
- il secondo parametro è il parametro da trasmettere alla richiesta SQL. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass=Nothing]. Pertanto, qui si passa un puntatore nullo.
- Il risultato è di tipo IList. Gli oggetti di questo elenco sono indicati dall'attributo [resultMap] del comando SQL-select: [resultMap="article"]. «articolo» è un nome di mappatura:
La classe associata a questo mapping è [istia.st.articles.dao.Article]. In definitiva, la variabile [articles] definita in precedenza è un elenco di oggetti [ istia.st.articles.dao.Article]. Abbiamo quindi ottenuto l'intera tabella [ARTICLES] con un'unica istruzione. Se la tabella [ARTICLES] è vuota, si ottiene un oggetto [IList] con 0 elementi.
Per eseguire il comando SqlMap [getArticleById], si scriverà:
- il metodo [QueryForObject] consente di ottenere il risultato di un comando SELECT che restituisce una sola riga
- il primo parametro è il nome del comando SqlMap da eseguire
- il secondo parametro è il parametro da trasmettere alla richiesta SQL. Deve corrispondere all’attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass="int"]. Pertanto, qui si passa un numero intero che rappresenta il numero dell'articolo cercato.
- Il risultato è di tipo Object. Se il comando SELECT non ha restituito alcuna riga, il risultato è un puntatore nullo (nothing).
Per eseguire il comando SqlMap [insertArticle], si scriverà:
- il metodo [Insert] consente di eseguire i comandi SQL e INSERT
- il primo parametro è il nome del comando SqlMap da eseguire
- il secondo parametro è il parametro da trasmettere a quest'ultimo. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass="istia.st.articles.dao.Article"]. Pertanto, qui si passa un oggetto di tipo [istia.st.articles.dao.Article].
Per eseguire il comando SqlMap [deleteArticle], si scriverà:
- il metodo [Delete] consente di eseguire i comandi SQL DELETE
- il primo parametro è il nome del comando SQL da eseguire
- il secondo parametro è il parametro da trasmettere a quest'ultimo. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass="int"]. Pertanto, qui si passa il numero dell'articolo da eliminare.
- Il risultato del metodo [Delete] è il numero di righe eliminate
Analogamente, per eseguire il comando SqlMap [clearAllArticles], si scriverà:
Per eseguire il comando SqlMap [modifyArticle], si digiterà:
- il metodo [Update] consente di eseguire i comandi SQL e UPDATE
- il primo parametro è il nome del comando SqlMap da eseguire
- il secondo parametro è il parametro da trasmettere a quest'ultimo. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass="istia.st.articles.dao.Article"]. Pertanto, qui si passa un oggetto di tipo [istia.st.articles.dao.Article].
- Il risultato del metodo [Update] è il numero di righe modificate.
Analogamente, per eseguire il comando SqlMap [changerStockArticle], si scriverà:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' aggiornamento
dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
- il secondo parametro corrisponde all'attributo [parameterClass] del comando SqlMap. In [articles.xml], si ha [parameterClass="Hashtable"]. Il comando SQL, configurato con [changerStockArticle], utilizza i parametri di [id, mouvement]. Pertanto, in questo caso si passa un dizionario contenente queste due chiavi.
2.5.6.6. Il codice della classe [ArticlesDaoSqlMap]
Alla luce delle spiegazioni precedenti, siamo ora in grado di scrivere la seguente nuova classe di implementazione [ArticlesDaoSqlMap]:
Option Explicit On
Option Strict On
Imports System
Imports IBatisNet.DataMapper
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlMap
Implements IArticlesDao
' campi privati
Dim mappeur As SqlMapper = Mapper.Instance
' elenco di tutti gli articoli
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
SyncLock Me
Try
Return mappeur.QueryForList("getAllArticles", Nothing)
Catch ex As Exception
Throw New Exception("Echec de l'obtention de tous les articles : [" + ex.ToString + "]")
End Try
End SyncLock
End Function
' aggiunta di un articolo
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle: articolo da aggiungere
' inserimento
mappeur.Insert("insertArticle", unArticle)
Return 1
Catch ex As Exception
Throw New Exception("Echec de l'ajout de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
End Try
End SyncLock
End Function
' elimina un articolo
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id: ID dell'articolo da eliminare
' eliminazione
Return mappeur.Delete("deleteArticle", idArticle)
Catch ex As Exception
Throw New Exception("Erreur lors de la suppression de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
End Try
End SyncLock
End Function
' modifica un articolo
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' aggiornamento
Return mappeur.Update("modifyArticle", unArticle)
Catch ex As Exception
Throw New Exception("Erreur lors de la mise à jour de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
End Try
End SyncLock
End Function
' ricerca di un articolo
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: ID dell'articolo cercato
Return CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
Catch ex As Exception
Throw New Exception("Erreur lors de la recherche de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
End Try
End SyncLock
End Function
' eliminazione di tutti gli articoli
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
SyncLock Me
Try
mappeur.Delete("clearAllArticles", Nothing)
Catch ex As Exception
Throw New Exception("Erreur lors de l'effacement de la table des articles : [" + ex.ToString + "]")
End Try
End SyncLock
End Sub
' modifica delle giacenze di un articolo
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' ID: ID dell'articolo di cui si modifica la giacenza
' movimento: movimento di magazzino
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' aggiornamento
Return mappeur.Update("changerStockArticle", paramètres)
Catch ex As Exception
Throw New Exception(String.Format("Erreur lors du changement de stock [{0},{1}] : {2}", idArticle, mouvement, ex.ToString))
End Try
End SyncLock
End Function
End Class
End Namespace
Si invita il lettore a leggere questo codice alla luce delle spiegazioni fornite per il codice API e SqlMap. È interessante notare che l'utilizzo di [SqlMap] ha ridotto notevolmente la quantità di codice da scrivere.
2.5.6.7. Generazione dell'assembly del livello [dao]
Il nuovo progetto Visual Studio presenta la seguente struttura:

Si noti la presenza degli “assembly” necessari a SqlMap nei riferimenti del progetto. Questi DLL sono stati collocati nella cartella [bin] del progetto. Il progetto è configurato per generare un DLL denominato [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. Test Nunit del livello [dao]
2.5.6.8.1. La classe di test NUnit
La classe di test Nunit della classe di implementazione [ArticlesDaoSqlMap] è la stessa di quella della classe [ArticlesDaoPlainODBC] (cfr. paragrafo 2.3.3.2). Seguiamo una procedura analoga per preparare il test Nunit della classe [ArticlesDaoSqlMap]:
- nella cartella Visual Studio del progetto [dao-sqlmap] creiamo la cartella [test1] (a destra) copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] sostituiamo DLL e [webarticles-dao.dll] con DLL e [webarticles-dao.dll] generati dal progetto [dao-sqlmap].
- Aggiungiamo i file DLL necessari per SqlMap, nonché i file di configurazione analizzati [providers.config, sqlmap.config, properties.xml, articles.xml].
- Modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoSqlMap]:
Commenti:
- alla riga 7, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ArticlesDaoSqlMap]
- questa classe non ha un costruttore. Verrà utilizzato il costruttore predefinito.
2.5.6.8.2. Tests
La tabella [ARTICLES] della fonte dati Firebird è popolata con i seguenti elementi:

Siamo pronti per i test. Utilizzando l'applicazione [Nunit-Gui], carichiamo DLL e [test-webarticles-dao.dll] dalla cartella [test1] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Nonostante il nome [NUnitTestArticlesDaoArrayList] assegnato inizialmente alla classe di test, è proprio la classe [ArticlesDaoSqlMap] ad essere qui testata. La schermata mostra che abbiamo recuperato correttamente gli articoli che avevamo inserito nella tabella [ARTICLES]. Ora eseguiamo tutti i test:

Il lettore che visualizza questo documento sullo schermo potrà notare che alcuni test hanno avuto esito positivo (colore verde), mentre altri sono falliti (colore rosso). I test che non sono andati a buon fine sono i test [testArticleAbsent] e [testChangerStockArticle]. Dopo lunghe ricerche, sembra che le cause di questi errori siano le seguenti:
- nel test [testArticleAbsent], viene richiesto di modificare un articolo che non esiste. A tal fine si utilizza il metodo [modifieArticle], che restituisce il numero di righe modificate, ovvero 0 o 1. In questo caso, il valore dovrebbe essere 0. Invece, si verifica un'eccezione di tipo [IBatisNet.Common.Exceptions.ConcurrentException].
- In [changerStockArticle] si verifica nuovamente un'operazione di tipo [update]. Si tratta di ridurre una giacenza di una quantità superiore alla giacenza stessa. A tal fine si utilizza il metodo [changerStockArticle], che restituisce il numero di righe modificate, ovvero 0 o 1. Il comando SQL è stato scritto per evitare un aggiornamento (cfr. comando SQL "changerStockArticle" in articles.xml) che renderebbe lo stock negativo. In questo caso ci si aspetta di ottenere 0 come risultato del metodo [changerStockArticle]. Ancora una volta, si verifica un'eccezione di tipo [IBatisNet.Common.Exceptions.ConcurrentException].
Le possibili cause di errore sono numerose:
- il codice della classe [ArticlesDaoSqlMap] è errato. È possibile. Tuttavia, deriva dal porting di una classe Java che aveva funzionato correttamente con la versione Java di SqlMap.
- la versione .NET di SqlMap presenta un bug
- il driver ODBC di Firebird presenta un bug
- ...
In mancanza di certezze, aggireremo l’ostacolo intercettando la famosa eccezione [IBatisNet.Common.Exceptions.ConcurrentException]. Il nuovo codice della classe [ArticlesDaoSqlMap] diventa il seguente:
Le modifiche si trovano alle righe: 28, 41, 69. Per le operazioni SQL di tipo [UPDATE, DELETE], se si verifica un'eccezione di tipo [IBatisNet.Common.Exceptions.ConcurrentException], si restituisce 0 come risultato, indicando così che nessuna riga è stata modificata o eliminata. A questo punto, il DLL del progetto viene rigenerato, inserito nella cartella [test1] e i test NUnit vengono riavviati:

Questa volta è quella buona. D'ora in poi lavoreremo con questo DLL.
2.5.6.9. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
2.5.6.9.1. fonte dati ODBC
Qui testiamo la fonte dati ODBC analizzata nel paragrafo 2.3.3.1. In questo caso viene utilizzata tramite SqlMap.
Seguiamo la procedura descritta nel paragrafo 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- nella cartella [bin], la DLL delvecchio livello [dao] viene sostituito dal DLL del nuovo livello [dao] implementato dalla classe [ArticlesDaoSqlMap]. Aggiungiamo i DLL necessari per Firebird e SqlMap:

- in [runtime], inseriamo i file di configurazione di SqlMap e [providers.config, sqlmap.config, properties.xml, articles.xml]:

- in [runtime], il file di configurazione [web.config] viene sostituito da un file che tiene conto della nuova classe di implementazione:
Commenti:
- alla riga 14 si associa al singleton [articlesDao] un'istanza della nuova classe [ArticlesDaoSqlMap]. Questa è l'unica modifica.
Siamo pronti per i test. Configuriamo il server web [Cassini] come nei test precedenti. Inizializziamo la tabella degli articoli con i seguenti valori:

Con un browser richiediamo l’URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES]:

Gli articoli [couteau] e [cuiller] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [fourchette] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.5.6.9.2. fonte dati MSDE
Qui testiamo la fonte dati MSDE esaminata nel paragrafo 2.4.3.1. In questo caso viene utilizzata tramite SqlMap. Seguiamo la stessa procedura di prima. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- il contenuto della cartella [bin] rimane invariato
- in [runtime], i file di configurazione di SqlMap e [providers.config, properties.xml] cambiano. I file di configurazione di [sqlmap.config, articles.xml] non cambiano.
- Il file [providers.config] configura un nuovo <provider>:
<?xml version="1.0" encoding="utf-8" ?>
<providers>
<clear/>
<provider
name="sqlServer1.1"
assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
connectionClass="System.Data.SqlClient.SqlConnection"
commandClass="System.Data.SqlClient.SqlCommand"
parameterClass="System.Data.SqlClient.SqlParameter"
parameterDbTypeClass="System.Data.SqlDbType"
parameterDbTypeProperty="SqlDbType"
dataAdapterClass="System.Data.SqlClient.SqlDataAdapter"
commandBuilderClass="System.Data.SqlClient.SqlCommandBuilder"
usePositionalParameters = "false"
useParameterPrefixInSql = "true"
useParameterPrefixInParameter = "true"
parameterPrefix="@"
/>
</providers>
Questo <provider> utilizza le classi .NET per l'accesso alle fonti di dati SQL Server. È integrato di serie nel file modello [providers.config] distribuito con SqlMap.
- Il file [properties.xml] definisce il <provider> della fonte MSDE e la relativa stringa di connessione:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<add key="provider" value="sqlServer1.1" />
<add
key="connectionString"
value="Data Source=portable1_tahe\msde140405;Initial Catalog=dbarticles;UID=admarticles;PASSWORD=mdparticles;"/>
</settings>
- in [runtime], il file di configurazione [web.config] rimane invariato.
Siamo pronti per i test. Il server web [Cassini] mantiene la sua configurazione abituale. Inizializziamo la tabella degli articoli della fonte MSDE con [EMS MS SQL Manager]:

Con un browser richiediamo URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES] con [EMS MS SQL Manager]:

Gli articoli [ballon foot] e [raquette tennis] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [rollers] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.5.6.9.3. fonte dati OleDb
Qui testiamo la fonte dati ACCESS presentata al paragrafo 2.4.5.1. In questo caso viene utilizzata tramite SqlMap. Seguiamo la stessa procedura di prima. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- il contenuto della cartella [bin] rimane invariato
- in [runtime], i file di configurazione di SqlMap e [providers.config, properties.xml] cambiano. I file di configurazione di [sqlmap.config, articles.xml] non cambiano.
- Il file [providers.config] configura un nuovo <provider>:
<?xml version="1.0" encoding="utf-8" ?>
<providers>
<clear/>
<provider
name="OleDb1.1"
enabled="true"
assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
connectionClass="System.Data.OleDb.OleDbConnection"
commandClass="System.Data.OleDb.OleDbCommand"
parameterClass="System.Data.OleDb.OleDbParameter"
parameterDbTypeClass="System.Data.OleDb.OleDbType"
parameterDbTypeProperty="OleDbType"
dataAdapterClass="System.Data.OleDb.OleDbDataAdapter"
commandBuilderClass="System.Data.OleDb.OleDbCommandBuilder"
usePositionalParameters = "true"
useParameterPrefixInSql = "false"
useParameterPrefixInParameter = "false"
parameterPrefix = ""
/>
</providers>
Questo <provider> utilizza le classi .NET per l'accesso alle fonti di dati OleDb. È integrato di serie nel file modello [providers.config] distribuito con SqlMap.
- Il file [properties.xml] definisce il <provider> della fonte OleDb e la relativa stringa di connessione:
<?xml version="1.0" encoding="utf-8" ?>
<settings>
<add key="provider" value="OleDb1.1" />
<add
key="connectionString"
value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;"/>
</settings>
- in [runtime], il file di configurazione [web.config] rimane invariato.
Siamo pronti per i test. Il server web [Cassini] mantiene la sua configurazione abituale. Inizializziamo la tabella degli articoli della fonte ACCESS nel modo seguente:

Con un browser richiediamo l'URL [http://localhost/webarticles/main.aspx]:

![]() |
Ora verifichiamo il contenuto della tabella [ARTICLES] con:

Gli articoli [pantalon] e [jupe] sono stati acquistati e le relative scorte sono state ridotte della quantità acquistata. L'articolo [manteau] non ha potuto essere acquistato poiché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a effettuare ulteriori test.
2.5.7. Conclusione
Concludiamo qui questo lungo articolo-tutorial. Cosa abbiamo fatto?
- Abbiamo implementato il livello [dao] di un’applicazione web a tre livelli in quattro modi diversi:
- utilizzando le classi di accesso .NET alle sorgenti ODBC
- utilizzando le classi di accesso .NET alle sorgenti SQL Server
- utilizzando le classi di accesso .NET ai sorgenti OleDb
- utilizzando classi di accesso di terze parti per accedere a un database Firebird
- ogni volta, abbiamo integrato il nuovo livello [dao] nell'applicazione a tre livelli [webarticles] [web, domain, dao] senza ricompilare nessuno dei livelli [web, domain]
- abbiamo infine introdotto lo strumento [SqlMap] che ci ha permesso di creare un livello [dao] in grado di adattarsi a diverse fonti di dati in modo trasparente per il codice. In questo modo, grazie a questo nuovo livello, abbiamo potuto utilizzare in successione le fonti di dati delle precedenti implementazioni da 1 a 3. Ciò è stato realizzato in modo trasparente tramite file di configurazione.
- Abbiamo dimostrato la grande flessibilità che gli strumenti Spring e SqlMap apportano alle applicazioni web a tre livelli.































































