2. Parte 2
2.1. Introduzione
Inizieremo rivedendo ciò che è stato fatto nella Parte 1, in particolare l'architettura a tre livelli [web, dominio, dao] utilizzata. Nella soluzione proposta, il livello [dao] era un livello di test: l'origine dati è stata implementata utilizzando un oggetto [ArrayList]. In questo articolo ci concentreremo sul livello [DAO], presentando varie possibili implementazioni dello stesso quando i dati si trovano in un DBMS.
Strumenti utilizzati:
- il DBMS Firebird — vedere Appendice, Sezione 3.5.
- il DBMS MSDE (Microsoft Data Engine) — vedi Appendice, Sezione 3.12.
- IBExpert, Personal Edition, per l'amministrazione grafica del DBMS Firebird — vedere Appendice, Sezione 3.6.
- EMS MS SQL Manager per l'amministrazione grafica del DBMS MSDE — vedi Appendice, Sezione 3.14.
- Ibatis SqlMap per il livello di accesso ai dati del DBMS — vedi sezione 2.5.6.2.
Su una scala che va da principiante a intermedio ad avanzato, questo documento rientra nella categoria [intermedio-avanzato]. Per comprenderlo sono necessari vari prerequisiti. Alcuni di questi si possono trovare nei documenti che ho scritto. In tali casi, li cito. Va da sé che questo è solo un suggerimento e che il lettore è libero di utilizzare le risorse che preferisce.
- Linguaggio VB.NET: [Introduzione a VB.NET attraverso esempi]
- Programmazione web in VB.NET: [Sviluppo web con ASP.NET 1.1]
- Utilizzo dell'IoC di Spring: [Spring IoC per .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 - Recensione
Di seguito presentiamo i componenti dell'applicazione web di e-commerce semplificata descritta nella Parte 1. Questa applicazione consente agli utenti web di:
- visualizzare un elenco di articoli da un database
- aggiungerne alcuni a un carrello elettronico
- confermare il carrello. Questa conferma aggiorna semplicemente i livelli di inventario degli articoli acquistati nel database.
2.2.1. Viste dell'applicazione
Le diverse viste presentate all'utente sono le seguenti:
![]() |
![]() |
![]() |
- la vista [ERRORI], 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 tramite l'uso di interfacce
- L'integrazione dei diversi livelli è stata implementata utilizzando Spring
- Ogni livello ha il proprio namespace: web (livello UI), domain (livello business) e dao (livello di accesso ai dati).
L'applicazione segue un'architettura MVC (Model-View-Controller). Se ci riferiamo al diagramma a livelli sopra riportato, l'architettura MVC si inserisce in esso come segue:
![]() |
L'elaborazione di una richiesta del client segue questi passaggi:
- Il client invia una richiesta al controller. Questo controller è, in questo caso, una pagina .aspx che svolge un ruolo specifico. Gestisce tutte le richieste dei client. È il punto di ingresso dell'applicazione. È la C in MVC.
- Il controller elabora questa richiesta. Per farlo, potrebbe aver bisogno dell’assistenza del livello business, noto come la M nella struttura MVC.
- Il controller riceve una risposta dal livello di business. La richiesta del cliente è stata elaborata. Ciò può innescare diverse possibili risposte. Un classico esempio è
- 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 molto spesso di una pagina contenente elementi dinamici. Il controller fornisce questi alla vista.
- La vista viene inviata al cliente. Questa è la V in MVC.
2.2.3. Il Modello
La M in MVC è costituita dai seguenti elementi:
2.2.3.1. Il database
Il database contiene una sola tabella denominata ARTICLES, generata utilizzando 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
);
/* constraints */
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);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chiave primaria che identifica in modo univoco un articolo | |
nome dell'elemento | |
il suo prezzo | |
scorte attuali | |
il livello di scorte al di sotto del quale è necessario effettuare un riordino |
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 nel proprio file "assembly":
assembly | contenuto | ruolo |
webarticles-dao | - [IArticlesDao]: l'interfaccia per accedere al livello [dao]. Questa è l'unica interfaccia visibile al livello [domain]. Non ne vede altre. - [Article]: classe che definisce un articolo - [ArticlesDaoArrayList]: classe di implementazione dell'interfaccia [IArticlesDao] che utilizza una classe [ArrayList] | livello di accesso ai dati - situato interamente all'interno del livello [dao] dell'architettura a 3 livelli dell'applicazione web |
webarticles-domain | - [IArticlesDomain]: l'interfaccia per l'accesso al livello [domain]. È l'unica interfaccia visibile al livello web. Non ne vede altre. - [ArticlePurchases]: una classe che implementa [IArticlesDomain] - [Purchase]: una classe che rappresenta l'acquisto di un cliente - [Cart]: una classe che rappresenta il totale degli acquisti di un cliente | rappresenta il modello di acquisto web - risiede interamente nel livello [domain] dell'architettura a 3 livelli dell'applicazione web |
2.2.4. Distribuzione e test dell'applicazione [webarticles]
2.2.4.1. Distribuzione
Distribuisciamo l'applicazione sviluppata nella Parte 1 di questo articolo in una cartella denominata [runtime]:
![]() | ![]() |
![]() |
Commenti:
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:
- le DLL per i tre livelli [webarticles-dao.dll], [webarticles-domain.dll], [webarticles-web.dll]
- i file richiesti da Spring [Spring-Core.*], [log4net.dll]
- la cartella [views], che contiene il codice di presentazione per le varie viste.
- La presenza di file di codice .vb non è necessaria poiché le loro versioni compilate si trovano nelle DLL.
2.2.4.2. Test
Configuriamo il server web [Cassini] come segue:

con:
Percorso fisico: D:\data\serge\work\2004-2005\aspnet\webarticles-010405\runtime\
Percorso virtuale: /webarticles
Utilizzando un browser, richiediamo l'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. Dalla vista sopra riportata, utilizziamo i collegamenti del menu per eseguire le operazioni. Eccone alcune. La colonna di sinistra rappresenta la richiesta del client, mentre quella di destra rappresenta la risposta inviata al client.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. Il livello [DAO] rivisitato
Nella nostra prima implementazione del livello [dao], l'interfaccia di accesso ai dati [IArticlesDao] era stata implementata da una classe che memorizzava gli elementi in un oggetto [ArrayList]. Questo ci ha permesso di evitare di complicare eccessivamente questo livello e di dimostrare che contava solo la sua interfaccia, non la sua implementazione. Siamo così riusciti a costruire un'applicazione web funzionante. Questa applicazione ha tre livelli: [web], [domain] e [dao]. Qui proporremo diverse implementazioni del livello [dao]. Ognuna di esse può sostituire l'attuale livello [dao] senza alcuna modifica ai livelli [domain] e [web]. Questa flessibilità si ottiene perché:
- il livello [domain] non fa riferimento a una classe concreta ma a un'interfaccia [IArticlesDao]
- grazie a Spring, siamo stati in grado di nascondere il nome della classe che implementa l'interfaccia [IArticlesDao] dal livello [domain].
2.2.5.1. Elementi del livello [dao]
Esaminiamo alcuni degli elementi del livello [dao] che saranno mantenuti nelle nuove implementazioni:
- - [IArticlesDao]: l'interfaccia per accedere al livello [dao]
- - [Article]: la 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
' private fields
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' id article
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
' item name
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
' item price
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
' current stock item
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
' minimum stock item
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
' default builder
Public Sub New()
End Sub
' builder with properties
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
' article identification method
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Questa classe fornisce:
- un costruttore per impostare le 5 informazioni relative a un articolo: [id, nome, prezzo, stockAttuale, stockMinimo]
- proprietà pubbliche per la lettura e la scrittura delle 5 informazioni.
- una convalida dei dati inseriti per l'articolo. Se i dati non sono validi, viene generata un'eccezione.
- un metodo toString che restituisce il valore di un articolo come stringa. Questo è 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
' list of all items
Function getAllArticles() As IList
' add an article
Function ajouteArticle(ByVal unArticle As Article) As Integer
' deletes an article
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modify an article
Function modifieArticle(ByVal unArticle As Article) As Integer
' search for an article
Function getArticleById(ByVal idArticle As Integer) As Article
' deletes all articles
Sub clearAllArticles()
' changes the stock of an item
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
I ruoli dei vari metodi nell'interfaccia sono i seguenti:
restituisce tutti gli articoli dall'origine dati | |
cancella l'origine dati | |
restituisce l'oggetto [Article] identificato dal suo numero | |
consente di aggiungere un articolo all'origine dati | |
consente di modificare un articolo nell'origine dati | |
consente di eliminare un elemento dall'origine dati | |
consente di modificare la quantità di un articolo nell'origine dati |
L'interfaccia fornisce ai programmi client una serie di metodi definiti esclusivamente dalle loro firme. Non si occupa di come questi metodi saranno effettivamente implementati. Ciò garantisce flessibilità all'applicazione. Il programma client effettua chiamate su un'interfaccia piuttosto che 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 sorgente ODBC. Sappiamo che su Windows, praticamente tutti i DBMS presenti sul mercato dispongono di un driver ODBC. Il vantaggio di questa soluzione è che è possibile cambiare DBMS in modo trasparente per l'applicazione. Lo svantaggio è che un driver ODBC che sfrutta solo le funzionalità comuni a tutti i DBMS è generalmente meno efficiente di un driver scritto specificamente per sfruttare appieno il potenziale di un particolare DBMS. Vedere la Sezione 3.7 per un esempio di creazione di una sorgente ODBC.
2.3.1. Il codice
2.3.1.1. Lo scheletro
La classe [ArticlesDaoPlainODBC] implementa l'interfaccia [IArticlesDao] come segue:
Commenti:
- Riga 3: importa lo spazio dei nomi contenente le classi .NET per l'accesso alle origini ODBC
- Riga 11: memorizza la connessione alla sorgente ODBC
- Riga 12: memorizza il nome DSN dell'origine dati
- Righe 13–19: variabili private di tipo [OdbcCommand] che definiscono le query SQL utilizzate dai vari metodi della classe
- Righe 22–27: il costruttore. Riceve gli elementi necessari per costruire l'oggetto [OdbcConnection] che collegherà il codice alla fonte dati ODBC
- righe 29–31 – il metodo per aggiungere un articolo
- righe 33–35 – il metodo per modificare la disponibilità di un articolo
- righe 37–39 – il metodo che elimina tutti gli articoli dall'origine dati ODBC
- righe 41-43: il metodo che recupera l'elenco di tutti gli articoli dall'origine ODBC
- righe 45–47 – il metodo che recupera un articolo specifico
- righe 49-51 – il metodo che consente di modificare determinati campi di un articolo di cui si conosce il numero
- righe 53-55 – il metodo che consente di eliminare un articolo in base al suo numero
- righe 57-60 - metodo di utilità per eseguire un [SELECT] sull'origine dati e restituire il risultato
- righe 62-64 – metodo di utilità per eseguire un [INSERT, UPDATE, DELETE] sull'origine dati e restituire il risultato
2.3.1.2. Il
Commenti:
- Riga 2 - Il costruttore riceve le tre informazioni necessarie per connettersi a una sorgente ODBC: il nome DSN della sorgente, il nome utente con cui connettersi e la password associata.
- Riga 8 - Il nome DSN della sorgente viene memorizzato in modo da poter essere incluso nei messaggi di errore.
- Riga 9 - Viene istanziato l'oggetto [OdbcConnection]. Una connessione istanziata non è una connessione aperta. Il metodo [open] viene utilizzato per aprire la connessione.
- Righe 12–19 – Prepariamo le query SQL negli oggetti [OdbcCommand]. Questo ci eviterà di doverle ricostruire ogni volta che ne avremo bisogno. I parametri formali ? nelle query saranno sostituiti con valori effettivi quando la query verrà eseguita.
2.3.1.3. Il metodo executeQuery
Commenti:
- Il metodo [executeQuery] è un metodo di utilità che:
- esegue una query [SELECT id, name, price, currentStock, minimumStock from ARTICLES ...] sull'origine dati
- restituisce il risultato come un elenco di oggetti [Article]
- Riga 1 - L'unico parametro del metodo è l'oggetto [OdbcCommand] contenente la query [Select] da eseguire.
- Riga 7 - Viene aperta la connessione. Verrà chiusa alla riga 29 indipendentemente dal fatto che si sia verificato o meno un errore.
- Riga 9 - Viene istanziato l'oggetto [OdbcDataReader] necessario per elaborare il risultato della [Select]
- Righe 13–23 – Ogni riga risultante dalla query [Select] viene inserita in un oggetto [Article], che viene poi aggiunto agli altri articoli in un [ArrayList]
- L'elenco degli elementi viene restituito alla riga 25
- Non vengono gestite eccezioni. Devono essere gestite dal codice che chiama questo metodo.
2.3.1.4. Il metodo executeUpdate
Commenti:
- Il metodo riceve un oggetto [OdbcCommand] contenente una query 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 query di aggiornamento viene eseguita alla riga 7. Il risultato, ovvero il numero di righe nella tabella ARTICLES modificate dalla query, viene restituito immediatamente.
2.3.1.5. Il metodo addArticle
Commenti:
- Riga 1 - Il metodo riceve l'elemento da aggiungere all'origine dati ODBC. Restituisce il numero di righe interessate da questa operazione, ovvero 1 o 0
- Righe 3 e 20 - Il metodo è sincronizzato. Questo vale per tutti i metodi di accesso ai dati. Ciò significa che solo un thread alla volta può lavorare sull'origine dati. Probabilmente si tratta di un approccio troppo conservativo. Esistono alternative migliori, in particolare includere queste operazioni all'interno di transazioni. In questo caso, il DBMS gestisce l'accesso concorrente. Non abbiamo voluto introdurre il concetto di transazioni in questa fase. Spring ci offre la possibilità di introdurle nel livello [domain]. Potremmo avere l'opportunità di riprendere questo argomento in un altro articolo.
- Le righe 5-12 assegnano valori ai parametri formali della query per l'oggetto [insertCommand] inizializzato dal costruttore. Ecco di nuovo la query:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
I 5 valori richiesti per la query sono forniti dalle righe 7–11.
- Righe 13–19: la query viene eseguita. Se ha esito positivo, 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'elemento da modificare dall'origine dati ODBC. Restituisce il numero di righe interessate da questa operazione, ovvero 1 o 0
- Qui è possibile inserire i commenti del metodo [addItem]
2.3.1.7. Il metodo deleteArticle
Commenti:
- Riga 1 - Il metodo riceve l'ID dell'elemento da eliminare dall'origine dati ODBC. Restituisce il numero di righe interessate da questa operazione, ovvero 1 o 0
- I commenti relativi al metodo [addItem] possono essere inseriti qui
2.3.1.8. Il metodo getAllArticles
Commenti:
- Riga 1 - Il metodo non accetta parametri. Restituisce un elenco di tutti gli elementi provenienti dall'origine dati ODBC
- La query [Select] che recupera tutti i record viene passata al metodo [executeQuery] - riga 6
- L'elenco risultante viene restituito alla riga 8
- Le righe 9–12 gestiscono eventuali eccezioni
2.3.1.9. Il metodo getArticleById
Commenti:
- Riga 1 - Il metodo riceve l'ID dell'elemento desiderato come parametro. Restituisce l'elemento se viene trovato nella sorgente ODBC; in caso contrario, restituisce il riferimento [nothing].
- La query [Select] che richiede l'elemento viene inizializzata nelle righe 5–8
- viene eseguita alla riga 12 - si ottiene un elenco di elementi
- se questo elenco è vuoto, alla riga 14 viene restituito il riferimento [nothing]
- Altrimenti, l'unico elemento dell'elenco viene visualizzato alla riga 16
- Le righe 17–20 gestiscono eventuali eccezioni
2.3.1.10. Il metodo clearAllArticles
Commenti:
- Riga 1 - Il metodo non accetta parametri e non restituisce alcun valore
- Riga 6 - Viene eseguita la query per eliminare tutti gli elementi
- Righe 7–10: Gestione delle eccezioni
2.3.1.11. Il metodo changerStockArticle
Commenti:
- Riga 1 - Il metodo riceve come parametri il numero dell'articolo per il quale è necessario modificare lo stock, nonché l'incremento dello stock (positivo o negativo). Restituisce il numero di righe modificate dall'operazione, ovvero 0 o 1.
- Righe 5–10: Viene inizializzata la query [updateStockCommand]. Ecco 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, dopo la modifica, rimane >=0.
- La query per aggiornare lo stock dell'articolo viene eseguita alla riga 13 e il risultato viene restituito
- righe 14–18, dove gestiamo eventuali eccezioni
2.3.2. Generazione dell'assembly del livello [DAO]
Il progetto Visual Studio per questa nuova versione del livello [dao] ha la seguente struttura:

Il progetto è configurato per generare una DLL denominata [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. Test NUnit per il livello [dao]
2.3.3.1. Creazione di un'origine dati ODBC-Firebird in
Per testare il nostro nuovo livello [DAO], abbiamo bisogno di una sorgente dati ODBC e quindi di un database. Stiamo utilizzando il DBMS Firebird (Sezione 3.5). Utilizzando IBExpert (Sezione 3.6), creiamo il seguente database degli articoli:
![]() | ![]() |
L'amministratore di questo database sarà l'utente [SYSDBA] con la password [masterkey]. Creiamo alcuni record:

Ora creiamo la seguente sorgente ODBC Firebird (vedere la sezione 3.7):
![]() |
La sorgente ODBC creata presenta le seguenti caratteristiche:
- Nome DSN: odbc-firebird-articles
- ID connessione: SYSDBA
- password associata: masterkey
2.3.3.2. La classe di test NUnit per il livello [dao]
Abbiamo già scritto una classe di test per il livello [dao] inizialmente realizzato. Se il lettore 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
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' retrieve an instance of the Spring object manufacturer
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' request instantiation of articlesdao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
Possiamo notare che nel metodo <Setup()> chiediamo a Spring un riferimento al singleton denominato [articlesdao] di tipo [IArticlesDao], ovvero di tipo interfaccia. Il singleton [articlesdao] è stato 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 o ricompilazioni.
- Nella cartella Visual Studio del nostro nuovo livello [dao], creare la cartella [tests] (mostrata in basso a destra) copiando la cartella [bin] dal progetto di test del livello [dao] iniziale (mostrato 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], sostituire il file DLL [webarticles-dao.dll] del vecchio livello [dao] con il file DLL [webarticles-dao.dll] del nuovo livello [dao]
- Modifica il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoPlainODBC]:
Commenti:
- alla riga 6, l'oggetto [articlesdao] è ora associato a un'istanza della classe [ArticlesDaoPlainODBC]
- Questa classe ha un costruttore con tre argomenti:
- il nome della sorgente DSN - riga 8
- l'identità utilizzata per accedere al database - riga 11
- la password associata a questa identità - riga 14
Qui stiamo utilizzando le informazioni della sorgente ODBC-Firebird che abbiamo creato in precedenza.
2.3.3.3. Test
Ora siamo pronti per eseguire i test. Utilizzando l'applicazione [Nunit-Gui], carichiamo la DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Guardando lo screenshot qui sopra, potremmo pentirci del nome [NUnitTestArticlesDaoArrayList] inizialmente assegnato alla classe di test. È fuorviante. In realtà è la classe [ArticlesDaoPlainODBC] che viene testata qui. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che abbiamo inserito nella tabella [ARTICLES]. Ora, eseguiamo tutti i test:

Nella finestra a sinistra, vediamo l'elenco dei metodi testati. Il colore del punto che precede il nome di ciascun metodo indica se il metodo è stato superato (verde) o ha fallito (rosso). I lettori che visualizzano questo documento sullo schermo vedranno che tutti i test hanno avuto esito positivo.
2.3.3.4. Conclusione
Abbiamo appena dimostrato che:
- poiché la classe di test NUnit non faceva riferimento a una classe ma a un'interfaccia;
- perché il nome esatto della classe che istanzia l'interfaccia è stato fornito in un file di configurazione e non nel codice;
- perché Spring si è occupato di istanziare la classe e di fornire un riferimento ad essa nel codice di test;
quindi, il codice di test scritto per il livello [dao] iniziale è rimasto valido per una nuova implementazione di quello stesso livello. Non abbiamo avuto bisogno di accedere al codice della classe di test. Abbiamo usato solo la sua versione compilata, quella generata durante il test del livello [dao] iniziale. Trarremo conclusioni simili quando arriverà il momento di integrare il nuovo livello [dao] nell'applicazione [webarticles].
2.3.4. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
2.3.4.1. Test di integrazione
Ricordiamo che la versione iniziale dell'applicazione [webarticles] è stata distribuita nella seguente cartella [runtime]:
![]() | ![]() |
![]() |
Si invitano i lettori a consultare la Sezione 2.2.4, che descrive in dettaglio le procedure di distribuzione dell'applicazione [webarticles]. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- Nella cartella [bin], la DLL del vecchio livello [dao] viene sostituita dalla 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 il singleton [articlesDao] a un'istanza della nuova classe [ArticlesDaoPlainODBC]. Questa è l'unica modifica. Abbiamo già riscontrato questo problema durante il test del nuovo livello [dao].
Siamo pronti per il test. Configuriamo il server web [Cassini] seguendo le istruzioni riportate nella sezione 2.2.4. Inizializziamo la tabella del prodotto [Firebird] con i seguenti valori:

Assicurarsi che il server web Cassini e il DBMS [Firebird] siano in esecuzione. Utilizzando un browser, richiediamo l'URL [http://localhost/webarticles/main.aspx]:

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

Gli articoli [ombrello] e [stivali] sono stati acquistati e le loro giacenze sono state ridotte della quantità acquistata. L'articolo [cappello] non ha potuto essere acquistato perché la quantità richiesta superava la quantità disponibile in magazzino. Invitiamo il lettore a eseguire ulteriori test.
2.3.4.2. Conclusione
Cosa abbiamo fatto?
- Siamo tornati alla versione di distribuzione della versione precedente;
- abbiamo sostituito la DLL del livello [dao] con una nuova versione. Le DLL dei livelli [web] e [domain] sono rimaste invariate;
- abbiamo modificato il file di configurazione [web.config] in modo che tenga conto della nuova classe di implementazione del livello [dao]
Tutto questo è pulito e rende molto facile far evolvere l'applicazione web. Queste importanti caratteristiche sono garantite da due scelte architetturali:
- accesso ai livelli tramite interfacce
- integrazione e configurazione dei livelli tramite Spring.
Stiamo ora proponendo 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 fornisce un DBMS chiamato MSDE, che è una versione limitata di SQL Server. Vedere l'appendice per le istruzioni su come ottenerlo e installarlo, sezione 3.12.
2.4.1. Il codice
La classe [ArticlesDaoSqlServer] è molto simile alla classe [ArticlesDaoPlainODBC] discussa in precedenza. Pertanto, evidenzieremo solo le modifiche apportate alla versione precedente:
- le classi richieste si trovano nello spazio dei nomi [System.Data.SqlClient] anziché nello spazio dei nomi [System.Data.Odbc]
- la connessione [OdbcConnection] è ora di tipo [SqlConnection]
- gli oggetti [OdbcCommand] sono ora di tipo [SqlCommand]
- la sintassi delle query SQL parametrizzate è cambiata. La query di inserimento ora ha questo aspetto:
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 [addItem] diventa quindi il seguente:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- Anche il costruttore viene modificato:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
'retrieve the name of the database passed as an argument
Me.databaseName = databaseName
'we instantiate the connection
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' prepare SQL requests
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
End Sub
Il costruttore ora accetta quattro parametri:
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
Il codice completo per la 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
' private fields
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
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
'retrieve the name of the database passed as an argument
Me.databaseName = databaseName
'we instantiate the connection
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As SqlDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return updateCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a rivedere questo codice alla luce dei commenti sulla 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 una DLL denominata [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. Test NUnit per il livello [dao]
2.4.3.1. Creazione di un'origine dati SQL Server
Per testare il nostro nuovo livello [dao], abbiamo bisogno di una fonte dati SQL Server e quindi del DBMS SQL Server. Utilizzeremo effettivamente il DBMS MSDE (Microsoft Data Engine) (Sezione 3.12), che è una versione di SQL Server limitata solo dal numero di utenti simultanei che supporta. Utilizzando [EMS MS SQL Manager] (sezione 3.14), creiamo il seguente database di prodotto in un'istanza MSDE denominata [portable1_tahe\msde140405]:
![]() | ![]() |

Il database è di proprietà dell'utente [mdparticles] con password [admarticles]. Il comando Transact-SQL per creare la 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 elementi:

2.4.3.2. La classe di test NUnit
La classe di test NUnit per la classe di implementazione [ArticlesDaoSqlServer] è la stessa di quella per la classe [ArticlesDaoPlainODBC] (vedere la sezione 2.3.3.2). Seguiamo un approccio simile per preparare il test NUnit per la classe:
- creiamo la cartella [tests] (a destra) nella cartella Visual Studio del progetto [dao-sqlserver] copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] del progetto [dao-sqlserver], sostituiamo la DLL [webarticles-dao.dll] con la DLL [webarticles-dao.dll] generata 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 [ArticlesDaoSqlServer]
- Questa classe ha un costruttore con quattro argomenti:
- il nome dell'istanza MSDE utilizzata - riga 9
- il nome del database - riga 12
- l'identità utilizzata per accedere al database - riga 15
- la password associata a questa identità - riga 18
Qui stiamo utilizzando le informazioni provenienti dalla sorgente MSDE che abbiamo creato in precedenza.
2.4.3.3. Test
Siamo pronti per eseguire i test. Utilizzando l'applicazione [Nunit-Gui], carichiamo la DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Sebbene la classe di test fosse inizialmente denominata [NUnitTestArticlesDaoArrayList] — un nome mantenuto poiché stiamo utilizzando la DLL [tests-webarticles-dao.dll] derivata da questa classe — è in realtà la classe [ArticlesDaoSqlserver] che viene testata qui. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che abbiamo inserito nella tabella [ARTICLES]. Ora, eseguiamo tutti i test:

Nella finestra a sinistra, vediamo 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). I lettori che visualizzano questo documento sullo schermo vedranno che tutti i test hanno avuto esito positivo.
2.4.4. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
Seguiamo la procedura spiegata nella sezione 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- Nella cartella [bin], la DLL del vecchio livello [dao] viene sostituita dalla 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 il singleton [articlesDao] a un'istanza della nuova classe [ArticlesDaoSqlServer]. Questa è l'unica modifica. Abbiamo già riscontrato questo problema durante il test del nuovo livello [dao]
Siamo pronti per il test. Manteniamo la stessa configurazione del server web [Cassini] di prima. Inizializziamo la tabella del prodotto [MSDE] con i seguenti valori:

Assicurarsi che il server web Cassini e il DBMS MSDE (in questo caso, l'istanza portable1_tahe\msde140405) siano in esecuzione. Utilizzando un browser, richiediamo l'URL [http://localhost/webarticles/main.aspx]:

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

Gli articoli [pallone da calcio] e [racchetta da tennis] sono stati acquistati e le loro scorte sono state ridotte della quantità acquistata. L'articolo [pattini a rotelle] non ha potuto essere acquistato perché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a eseguire ulteriori test.
2.4.5. La classe di implementazione [ArticlesDaoOleDb]
2.4.5.1. e 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 alla base delle origini OleDb è analogo a quello delle origini ODBC. Un programma che utilizza un'origine OleDb lo fa tramite un'interfaccia standard comune a tutte le origini OleDb. La modifica dell'origine OleDb comporta semplicemente la modifica del driver OleDb. Il codice stesso rimane invariato.
È possibile scoprire quali driver OleDb sono disponibili sul proprio computer utilizzando Visual Studio:
- visualizzare Server Explorer selezionando [Visualizza/Server Explorer]:

- Per aggiungere una nuova connessione, fare clic con il tasto destro del mouse su [Connessione dati] e selezionare l'opzione [Aggiungi connessione]. Si aprirà una procedura guidata in cui è possibile definire le impostazioni di connessione:

- Il riquadro [Provider] elenca i driver OLEDB disponibili. Per il nuovo livello [DAO], useremo il driver [Microsoft Jet 4.0 OLE DB Provider], che fornisce l'accesso ai database di Access.
- Usciamo temporaneamente da Visual Studio per creare il database ACCESS [articles.mdb] con la seguente tabella singola:

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

- Selezioniamo il driver [Microsoft Jet 4.0] e andiamo al pannello [Connessione]:

- Utilizzando il pulsante [1], selezioniamo il database ACCESS appena creato, quindi completiamo la configurazione della connessione facendo clic sul pulsante [Fine]. La connessione appena creata appare ora nell'elenco delle connessioni disponibili:

- Facendo doppio clic sulla tabella [ARTICLES] si accede al suo contenuto:

- È quindi possibile aggiungere, modificare o eliminare righe nella tabella.
- In Esplora server, selezionare la nuova connessione per accedere alla scheda Proprietà:

- È utile conoscere la stringa di connessione. La useremo per connetterci 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
- Da questa stringa, manterremo solo i seguenti elementi:
2.4.5.2. Il codice per la classe [ArticlesDaoOleDb]
La classe [ArticlesDaoOleDb] è molto simile alla classe [ArticlesDaoPlainODBC] discussa in precedenza. Pertanto, elencheremo solo le modifiche apportate alla versione precedente:
- le classi richieste si trovano nello spazio dei nomi [System.Data.OleDb] anziché nello spazio dei nomi [System.Data.Odbc]
- la connessione [OdbcConnection] è ora di tipo [OleDbConnection]
- gli oggetti [OdbcCommand] sono ora di tipo [OleDbCommand]
Il costruttore della classe accetta un unico parametro: la stringa di connessione al database:
' manufacturer
Public Sub New(ByVal connectString As String)
' connectString: source connection string OleDb: source connection string connectString: source connection string OleDb: source connection string
'we instantiate the connection
connexion = New OleDbConnection(connectString)
' prepare SQL requests
...
End Sub
Il codice completo per la 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
' private fields
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
' manufacturer
Public Sub New(ByVal connectString As String)
' connectString: source connection string OleDb: source connection string connectString: source connection string OleDb: source connection string
'we instantiate the connection
connexion = New OleDbConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As OleDbDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return sqlCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a rivedere questo codice alla luce dei commenti sulla 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 una DLL denominata [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. Test NUnit per il livello [dao]
2.4.5.4.1. La classe di test NUnit
La classe di test NUnit per la classe di implementazione [ArticlesDaoOleDb] è la stessa di quella per la classe [ArticlesDaoPlainODBC] (vedere la sezione 2.3.3.2). Seguiamo un approccio simile per preparare il test NUnit per la classe:
- creiamo la cartella [tests] (a destra) nella cartella Visual Studio del progetto [dao-oledb] copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- nella cartella [tests] del progetto [dao-oledb], sostituiamo la DLL [webarticles-dao.dll] con la DLL [webarticles-dao.dll] generata dal progetto [dao-oledb]
- Modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoOleDb]:
Commenti:
- alla 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 ACCESS OleDb - riga 9
2.4.5.4.2. Test
Siamo pronti per eseguire i test. Utilizzando l'applicazione [Nunit-Gui], carichiamo la DLL [test-webarticles-dao.dll] dalla cartella [tests] sopra indicata ed eseguiamo il test [testGetAllArticles]:

Nonostante il nome [NUnitTestArticlesDaoArrayList] inizialmente assegnato alla classe di test, è in realtà la classe [ArticlesDaoOleDb] che viene testata qui. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che abbiamo inserito nella tabella [ARTICLES]. Ora, eseguiamo tutti i test:

I lettori che visualizzano questo documento sullo schermo vedranno che tutti i test sono stati superati (colore verde).
2.4.5.5. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
Seguiamo la procedura spiegata nella sezione 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- Nella cartella [bin], la DLL del vecchio livello [dao] viene sostituita dalla 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 il singleton [articlesDao] a un'istanza della nuova classe [ArticlesDaoOleDb]. Questa è l'unica modifica.
Manteniamo la stessa configurazione del server web [Cassini] di prima. Inizializziamo la tabella dei prodotti con i seguenti valori:

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

![]() |
Ora controlliamo il contenuto della tabella [ARTICLES] utilizzando Access:

Gli articoli [pants] e [skirt] sono stati acquistati e le loro scorte sono state ridotte della quantità acquistata. L'articolo [coat] non ha potuto essere acquistato perché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a eseguire ulteriori test.
2.5. La classe di implementazione [ArticlesDaoFirebirdProvider]
2.5.1. Il provider Firebird-net
In precedenza abbiamo utilizzato un'origine dati [Firebird] tramite un driver ODBC. Sebbene i driver ODBC offrano un'elevata riutilizzabilità per il codice che li utilizza, sono meno efficienti dei driver scritti specificamente per il DBMS di destinazione. Il DBMS [Firebird] può essere utilizzato tramite una libreria di classi specifiche scaricabile dal sito web 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 per l'accesso al DBMS Firebird. L'installazione del pacchetto crea una cartella simile alla seguente:

Due elementi sono di nostro interesse:
- [FirebirdSql.Data.Firebird.dll]: l'assembly contenente le classi .NET per l'accesso al DBMS Firebird
- [FirebirdNETProviderSDK.chm]: la documentazione relativa a queste classi
Successivamente, per consentire a un progetto Visual Studio di utilizzare queste classi, faremo due cose:
- Inseriremo l'assembly [FirebirdSql.Data.Firebird.dll] nella cartella [bin] del progetto
- aggiungeremo questo stesso assembly ai riferimenti del progetto
2.5.2. Il codice per la classe [ArticlesDaoFirebirdProvider]
La classe [ArticlesDaoFirebirdProvider] è molto simile alla classe [ArticlesDaoSqlServer] discussa in precedenza. Pertanto, evidenzieremo solo le modifiche apportate rispetto a quella versione:
- Le classi richieste si trovano nello spazio dei nomi [FirebirdSql.Data.Firebird] anziché nello spazio dei nomi [System.Data.SqlClient]
- la connessione [SqlConnection] è ora di tipo [FbConnection]
- gli oggetti [SqlCommand] sono ora di tipo [FbCommand]
- Gli oggetti [SqlParameter] sono ora di tipo [FbParameter]
Il costruttore della classe accetta quattro parametri, che utilizza per costruire la stringa di connessione al database:
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: name of the SGBD host machine
' databaseName: path to database
' uid: identity of the user logging in
' password: your password
...
End Sub
Il codice completo per la 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
' private fields
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
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' server: name of the SGBD Firebird host machine
' databaseName: path to the database to be used
' uid: identity of the user connecting to the database
' password: your password
'retrieve the name of the database passed as an argument
Me.databasePath = databasePath
'we instantiate the connection
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As FbDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return updateCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Si invita il lettore a rivedere questo codice alla luce dei commenti sulla 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. Questa DLL è stata collocata nella cartella [bin] del progetto. Il progetto è configurato per generare una DLL denominata [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. Test NUnit per il livello [dao]
2.5.4.1. La classe di test NUnit
La classe di test NUnit per la classe di implementazione [ArticlesDaoFirebirdProvider] è la stessa di quella per la classe [ArticlesDaoPlainODBC] (vedere la sezione 2.3.3.2). Seguiamo un approccio simile per preparare il test NUnit per la classe [ArticlesDaoFirebirdProvider]:
- creiamo la cartella [tests] (a destra) nella cartella Visual Studio del progetto [dao-firebird-provider] copiando la cartella [bin] dal progetto di test del livello [dao-odbc] (a sinistra):
![]() | ![]() |
- Nella cartella [tests], sostituiamo la DLL [webarticles-dao.dll] con la DLL [webarticles-dao.dll] generata 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 con quattro argomenti
- il computer host del DBMS - riga 9
- il percorso del database Firebird - riga 12
- il login dell'utente che si connette - riga 15
- la sua password - riga 18
2.5.4.2. Test
La tabella [ARTICLES] nell'origine dati è popolata con i seguenti elementi (utilizzare IBExpert):

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

Nonostante il nome [NUnitTestArticlesDaoArrayList] inizialmente assegnato alla classe di test, è in realtà la classe [ArticlesDaoFirebirdProvider] che viene testata qui. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che abbiamo inserito nella tabella [ARTICLES]. Ora, eseguiamo tutti i test:

I lettori che visualizzano questo documento sullo schermo vedranno che tutti i test sono stati superati (verde). Ciò che non possono vedere è che i test sono stati eseguiti in modo significativamente più veloce rispetto al database degli articoli a cui si accedeva tramite un driver ODBC nella nostra prima implementazione.
2.5.5. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
Seguiamo la procedura già spiegata due volte, in particolare nella sezione 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- Nella cartella [bin], la DLL per il vecchio livello [dao] viene sostituita dalla DLL per il nuovo livello [dao] implementato dalla classe [ArticlesDaoFirebirdProvider]. Inseriamo lì anche la DLL richiesta da Firebird [FirebirdSql.Data.Firebird.dll]:

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

Utilizzando un browser, inseriamo l'URL [http://localhost/webarticles/main.aspx]:

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

Gli articoli [matita] e [blocco da 50 fogli] sono stati acquistati e le loro scorte sono state ridotte della quantità acquistata. L'articolo [penna stilografica] non ha potuto essere acquistato perché la quantità richiesta superava la quantità disponibile in magazzino. Invitiamo il lettore a eseguire 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] per la nostra applicazione [webarticles]. In ogni caso, siamo stati in grado di integrare il nuovo livello [dao] nell'applicazione [webarticles] senza ricompilare gli altri due livelli, [web] e [domain]. Questo risultato è stato ottenuto, come promemoria, attraverso due scelte architetturali:
- accesso ai livelli tramite interfacce
- integrazione dei livelli utilizzando Spring
Vogliamo fare un ulteriore passo avanti. Sebbene diverse, le nostre quattro implementazioni del livello [dao] presentano sorprendenti somiglianze. Una volta scritta la prima implementazione, le altre tre sono state create quasi interamente tramite copia-incolla e sostituzione di alcune parole chiave con altre. La logica, tuttavia, è rimasta invariata. Ci si potrebbe chiedere se fosse possibile avere un'unica implementazione che ci liberasse dai vari metodi di accesso ai dati. Ne abbiamo utilizzati quattro:
- accesso tramite un driver ODBC a un'origine dati ODBC
- accesso diretto a un database SQL Server
- accesso tramite un driver Ole DB a un'origine 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 dei dati. L'accesso ai dati avviene tramite:
- file di configurazione contenenti informazioni che definiscono la fonte dati e le operazioni da eseguire su di essa
- una libreria di classi che utilizza 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 sembra presentare alcuni bug (opinione personale che richiederebbe una verifica approfondita). Tuttavia, dato che lo strumento ha dato prova di sé sulla piattaforma Java, sembra valga la pena presentare la versione .NET.
2.5.6.2. Dove posso trovare l' e di IBATIS SqlMap?
Il sito web principale di Firebird è [http://www.ibatis.com/]. La pagina dei download offre i seguenti link:

Selezionare il link [Stable Binaries], che rimanda a [SourceForge.net]. Seguire la procedura di download fino al completamento. Si riceverà 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 un riferimento a ciascuno di questi file al progetto
2.5.6.3. File di configurazione di iBatis SqlMap
Una fonte dati [SqlMap] verrà definita utilizzando i seguenti file di configurazione:
- providers.config: definisce le librerie di classi da utilizzare per l'accesso ai dati
- sqlmap.config: definisce le impostazioni di connessione
- File di mappatura: definiscono le operazioni da eseguire sui dati
La logica alla base 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. Avremo anche bisogno di un oggetto [Command] per eseguire le query SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand, ecc. Nel file [providers.config], definiamo tutte le classi di cui abbiamo bisogno.
- Il file [sqlmap.config] definisce essenzialmente la stringa di connessione al database contenente i dati. La connessione al database verrà aperta istanziando la 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 nelle tabelle di dati e le classi .NET, le cui istanze conterranno queste righe
- le operazioni SQL da eseguire. Queste sono identificate da un nome. Il codice .NET esegue queste operazioni tramite i loro nomi, il che elimina tutto il codice SQL dal codice .NET.
2.5.6.4. I file di configurazione per il progetto [dao-sqlmap]
Esaminiamo la natura esatta dei file di configurazione di SqlMap utilizzando un esempio. Prenderemo in considerazione il caso in cui l'origine dati sia l'origine ODBC Firebird della sezione 2.3.3.1.
2.5.6.4.1. providers.config
Il file [providers.config] per una sorgente ODBC è il seguente:
Commenti:
- Il pacchetto [SqlMap] include un file [providers.config] che offre diversi provider standard. Il codice sopra riportato è tratto direttamente da questo file.
- Un <provider> ha un nome — riga 6 — che può essere qualsiasi cosa
- Un <provider> può essere abilitato [enabled=true] o disabilitato [enabled=false]. Se abilitato, la 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 contenente le classi definite alle righe 9-15
- riga 9 - classe da utilizzare per creare una connessione
- riga 10 - classe da utilizzare per creare un oggetto [Command] per l'esecuzione di comandi SQL
- riga 11 - classe da utilizzare per la gestione dei parametri di un comando SQL parametrizzato
- riga 12 - classe di enumerazione dei possibili tipi di dati per i campi della tabella
- riga 13 - nome della proprietà di un oggetto [Parameter] che contiene il tipo del valore di questo parametro
- riga 14 - nome della classe [Adapter] utilizzata per creare oggetti [DataSet] dall'origine dati
- riga 15 - nome della classe [CommandBuilder] che, quando associata a un oggetto [Adapter], genera automaticamente le sue proprietà [InsertCommand, DeleteCommand, UpdateCommand] dalla sua proprietà [SelectCommand]
- righe 16–19 – definiscono come vengono gestiti i comandi SQL parametrizzati. A seconda della situazione, potresti scrivere, ad esempio:
oppure
Nel primo caso, si tratta di parametri posizionali formali. I loro valori effettivi devono essere forniti nell'ordine dei parametri formali. Nel secondo caso, si tratta di parametri denominati. A un parametro di questo tipo viene fornito un valore specificandone il nome. L'ordine non ha più importanza.
- Riga 16 - indica che le sorgenti ODBC utilizzano parametri posizionali
- Righe 17–19 – riguardano i parametri con nome. Qui 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 sorgente ODBC. Non specifica alcuna sorgente ODBC. Il file [sqlmap.config] lo fa:
Commenti:
- Riga 3 - Definiamo un file di proprietà [properties.xml]. Questo file definisce coppie chiave-valore. Le chiavi possono essere qualsiasi cosa. Il valore associato a una chiave C viene ottenuto utilizzando la notazione ${C} in [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 - viene definita la chiave [connectionString]. Il suo valore è la stringa di connessione da utilizzare per aprire una connessione all'origine dati ODBC di Firebird.
- righe 4-7 - parametri di configurazione:
- riga 5 - le query SQL saranno identificate da un nome che può a sua volta far parte di uno spazio dei nomi. [useStatementNamespaces="false"] indica che gli spazi dei nomi non saranno utilizzati.
- riga 6 - SqlMap dispone di varie strategie di caching per ridurre al minimo l'accesso all'origine dati. [cacheModelsEnabled="false"] indica che non ne verrà utilizzata nessuna.
- Righe 9–13 – Vengono definite le proprietà dell'origine dati:
- riga 10 - nome del <provider> da [providers.config] da utilizzare
- Riga 11 - Stringa di connessione alla fonte dati
- riga 12 - gestore delle transazioni. Non l'abbiamo utilizzato in questo caso, ma abbiamo lasciato la riga perché era presente nel file di distribuzione standard.
- righe 14-16 – elenco dei file che definiscono le operazioni SQL da eseguire sull'origine dati.
- riga 15 - definisce il file di mappatura [articles.xml]
2.5.6.4.3. articles.xml
Questo file ha due scopi:
- Definire una mappatura degli oggetti per le tabelle dell'origine dati. Nei casi più semplici, ciò equivale ad associare una classe a una riga in una tabella.
- definire operazioni SQL parametrizzate e assegnare loro un nome.
Useremo il seguente file [articles.xml]:
Commenti:
- Righe 4-11 - Definiamo una mappatura tra una riga della tabella [ARTICLES] dell'origine dati e la classe [istia.st.articles.dao.Article]. Ogni colonna della tabella è associata a una proprietà della classe [Article]. Questa mappatura consente a [SqlMap] di costruire il risultato di un'operazione SQL SELECT. Ogni riga risultante dal SELECT verrà inserita in un oggetto [Article] secondo le regole di mappatura.
- Riga 5 - La mappatura è racchiusa in un tag <resultMap> e viene denominata utilizzando l'attributo [id="article"]. La classe associata è specificata dall'attributo [class="istia.st.articles.dao.Article"].
- Righe 14–44 – Vengono definite le operazioni SQL richieste
- Righe 16–18 – Viene definita un'operazione SELECT denominata [getAllArticles]
- riga 16 - l'operazione SELECT è denominata [name="getAllArticles"] e la mappatura da utilizzare è definita dall'attributo [resultMap="article"]. Ciò fa riferimento alla mappatura nelle righe 5–11
- riga 17 – testo del comando SQL da eseguire
- Righe 20–22 – Definiamo il comando SQL-Delete [clearAllArticles] per svuotare la tabella degli articoli.
- Righe 24–27 – Definiamo il comando SQL-Insert [insertArticle] per aggiungere un nuovo articolo alla tabella degli articoli. Si tratta di una query parametrizzata che utilizza gli elementi (#id#, #name#, #price#, #currentStock#, #minStock#). I valori per 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, name, price, currentStock, minimumStock) a cui fa riferimento il comando SQL parametrizzato.
- righe 29-31 - definiamo il comando SQL Delete [deleteArticle] destinato a cancellare un articolo il cui numero #value# è noto. Questo numero verrà passato come parametro: [parameterClass="int"]. Questa è una regola generale. Quando il parametro è univoco, viene fatto riferimento ad esso tramite la parola chiave #value# nel testo del comando SQL.
- Righe 33–35 – Definiamo il comando SQL-Update [modifyArticle] per modificare un articolo il cui numero è noto. Come per il comando [insertArticle], le cinque informazioni richieste proverranno dalle proprietà di un oggetto [istia.st.articles.dao.Article].
- Righe 37-39 – Definiamo il comando SQL-Select [getArticleById], che recupera il record di un articolo il cui numero è noto.
- Righe 41–43 – Definiamo il comando SQL-Update [changerStockArticle], che modifica il campo [stockactuel] di un articolo il cui numero è noto. Le due informazioni richieste – l’#id# dell’articolo e l’incremento di stock #mouvement# – si trovano in un dizionario: [parameterClass="Hashtable"]. Questo dizionario deve avere due chiavi: id e mouvement. I valori associati a queste due chiavi saranno utilizzati nel comando SQL.
2.5.6.4.4. Posizione dei file di configurazione
Prenderemo in considerazione due diversi scenari:
- Nel caso di un test Nunit, i file di configurazione [SqlMap] saranno collocati nella stessa cartella dei binari testati.
- Nel caso di un'applicazione web, saranno collocati nella radice dell'applicazione.
2.5.6.5. L'API SqlMap
Le classi SqlMap sono contenute in DLL che in genere si trovano nella cartella [bin] dell'applicazione:

Le applicazioni che utilizzano le classi SqlMap devono importare lo spazio dei nomi [IBatisNet.DataMapper]:
Tutte le operazioni SQL vengono eseguite tramite un singleton di tipo [Mapper], una classe nel namespace [IBatisNet.DataMapper]. Il singleton viene ottenuto come segue:
Per eseguire il comando SqlMap [getAllArticles], scriviamo:
- Il metodo [QueryForList] restituisce il risultato di un comando SELECT sotto forma di elenco
- Il primo parametro è il nome del comando SQL da eseguire (vedi articles.xml)
- Il secondo parametro è il parametro da passare alla query SQL. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass=Nothing]. Pertanto, qui passiamo un puntatore nullo.
- Il risultato è di tipo IList. Gli oggetti in questo elenco sono specificati dall'attributo [resultMap] del comando SQL-select: [resultMap="article"]. "article" è un nome di mappatura:
La classe associata a questa mappatura è [istia.st.articles.dao.Article]. In definitiva, la variabile [articles] definita sopra è un elenco di oggetti [istia.st.articles.dao.Article]. Abbiamo quindi recuperato l'intera tabella [ARTICLES] in un'unica istruzione. Se la tabella [ARTICLES] è vuota, otteniamo un oggetto [IList] con 0 elementi.
Per eseguire il comando SqlMap [getArticleById], scriviamo:
- Il metodo [QueryForObject] recupera 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 passare alla query SQL. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass="int"]. Pertanto, qui passiamo un numero intero che rappresenta l'ID dell'articolo che si sta cercando.
- Il risultato è di tipo Object. Se il comando SELECT non ha restituito alcuna riga, il risultato è un puntatore nullo (nulla).
Per eseguire il comando SqlMap [insertArticle], scriviamo:
- Il metodo [Insert] consente di eseguire comandi SQL INSERT
- Il primo parametro è il nome del comando SqlMap da eseguire
- Il secondo parametro è il parametro da passare al comando. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass="istia.st.articles.dao.Article"]. Pertanto, qui passiamo un oggetto di tipo [istia.st.articles.dao.Article].
Per eseguire il comando SqlMap [deleteArticle], scriviamo:
- Il metodo [Delete] consente di eseguire comandi SQL DELETE
- Il primo parametro è il nome del comando SQL da eseguire
- Il secondo parametro è il parametro da passare al comando. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass="int"]. Pertanto, qui passiamo l'ID dell'articolo da eliminare.
- Il risultato del metodo [Delete] è il numero di righe eliminate
Allo stesso modo, per eseguire il comando SqlMap [clearAllArticles], scriviamo:
Per eseguire il comando SqlMap [modifyArticle], scriviamo:
- Il metodo [Update] consente di eseguire comandi SQL UPDATE
- Il primo parametro è il nome del comando SqlMap da eseguire
- Il secondo parametro è il parametro da passare al comando. Deve corrispondere all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass="istia.st.articles.dao.Article"]. Pertanto, qui passiamo un oggetto di tipo [istia.st.articles.dao.Article].
- Il risultato del metodo [Update] è il numero di righe modificate.
Allo stesso modo, per eseguire il comando SqlMap [changerStockArticle], scriveremmo:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' update
dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
- Il secondo parametro corrisponde all'attributo [parameterClass] del comando SqlMap. In [articles.xml], abbiamo [parameterClass="Hashtable"]. Il comando SQL parametrizzato [changeItemStock] utilizza i parametri [id, movement]. Pertanto, qui passiamo un dizionario con queste due chiavi.
2.5.6.6. Il codice per la classe [ArticlesDaoSqlMap]
Seguendo le 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
' private fields
Dim mappeur As SqlMapper = Mapper.Instance
' list of all items
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
' add an item
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle : item to add
' insertion
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
' deletes an article
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id : id of item to be deleted
' delete
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
' modify an article
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' update
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
' article search
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: id of the item searched for
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
' delete all items
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
' change the stock of an item
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' id: id of the item whose stock is being changed
' movement: stock movement
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' update
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 invitano i lettori a rivedere questo codice alla luce delle spiegazioni fornite per l'API SqlMap. Vale la pena notare che l'uso di [SqlMap] ha ridotto significativamente la quantità di codice richiesta.
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" richiesti da SqlMap nei riferimenti del progetto. Queste DLL sono state collocate nella cartella [bin] del progetto. Il progetto è configurato per generare una DLL denominata [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. Test NUnit per il livello [dao]
2.5.6.8.1. La classe di test NUnit
La classe di test NUnit per la classe di implementazione [ArticlesDaoSqlMap] è la stessa di quella per la classe [ArticlesDaoPlainODBC] (vedere la sezione 2.3.3.2). Seguiamo un approccio simile per preparare il test NUnit per la classe [ArticlesDaoSqlMap]:
- creiamo la cartella [test1] (a destra) nella cartella Visual Studio del progetto [dao-sqlmap] copiando la cartella [tests] dal progetto [dao-odbc] (a sinistra):
![]() | ![]() |
- Nella cartella [tests], sostituiamo la DLL [webarticles-dao.dll] con la DLL [webarticles-dao.dll] generata dal progetto [dao-sqlmap].
- Aggiungiamo le DLL richieste da SqlMap e i file di configurazione menzionati [providers.config, sqlmap.config, properties.xml, articles.xml].
- Modifichiamo il file di configurazione [spring-config.xml] per istanziare la nuova classe [ArticlesDaoSqlMap]:
Commenti:
- 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. Test
La tabella [ARTICLES] nell'origine dati Firebird è popolata con i seguenti articoli:

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

Nonostante il nome [NUnitTestArticlesDaoArrayList] inizialmente assegnato alla classe di test, è in realtà la classe [ArticlesDaoSqlMap] che viene testata qui. Lo screenshot mostra che abbiamo recuperato correttamente gli articoli che abbiamo inserito nella tabella [ARTICLES]. Ora, eseguiamo tutti i test:

I lettori che visualizzano questo documento sullo schermo vedranno che alcuni test sono stati superati (verde) mentre altri hanno dato esito negativo (rosso). I test che hanno dato esito negativo sono [testArticleAbsent] e [testChangerStockArticle]. Dopo un'approfondita analisi, sembra che le cause di questi fallimenti siano le seguenti:
- In [testArticleAbsent], tentiamo di modificare un elemento che non esiste. A tal fine utilizziamo il metodo [modifieArticle], che restituisce il numero di righe modificate come 0 o 1. In questo caso, dovremmo ottenere 0. Invece, otteniamo un'eccezione di tipo [IBatisNet.Common.Exceptions.ConcurrentException].
- In [changerStockArticle], c'è un'altra operazione di tipo [update]. Questa comporta la riduzione delle scorte di un importo superiore alle scorte attuali. Per farlo, si utilizza il metodo [changerStockArticle], che restituisce il numero di righe modificate come 0 o 1. Il comando SQL è stato scritto per impedire un aggiornamento (vedere il comando SQL "changerStockArticle" in articles.xml) che comporterebbe un livello di stock negativo. In questo caso, ci aspettiamo di ottenere 0 come risultato del metodo [changerStockArticle]. Anche in questo caso, otteniamo un'eccezione di tipo [IBatisNet.Common.Exceptions.ConcurrentException].
Esistono molte possibili cause di errore:
- il codice nella classe [ArticlesDaoSqlMap] è errato. Questo è possibile. Tuttavia, proviene da un porting di una classe Java che aveva funzionato correttamente con la versione Java di SqlMap.
- la versione .NET di SqlMap è difettosa
- il driver ODBC di Firebird è difettoso
- ...
In assenza di certezze, aggireremo il problema intercettando l'eccezione [IBatisNet.Common.Exceptions.ConcurrentException]. Il nuovo codice per la 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], viene restituito 0 come risultato, indicando così che nessuna riga è stata modificata o eliminata. Una volta fatto ciò, la DLL del progetto viene rigenerata, collocata nella cartella [test1] e i test NUnit vengono rieseguiti:

Questa volta funziona. Ora lavoreremo con questa DLL.
2.5.6.9. Integrazione del nuovo livello [dao] nell'applicazione [webarticles]
2.5.6.9.1. Origine dati ODBC
Qui testiamo l'origine dati ODBC discussa nella sezione 2.3.3.1. Qui viene utilizzata tramite SqlMap.
Seguiamo la procedura descritta nella sezione 2.3.4. Apportiamo le seguenti modifiche al contenuto della cartella [runtime]:
- Nella cartella [bin], la DLL per il vecchio livello [dao] viene sostituita dalla DLL per il nuovo livello [dao] implementato dalla classe [ArticlesDaoSqlMap]. Aggiungiamo le DLL necessarie per Firebird e SqlMap:

- in [runtime], inseriamo i file di configurazione di SqlMap [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:
- Le righe 14 associano il singleton [articlesDao] a un'istanza della nuova classe [ArticlesDaoSqlMap]. Questa è l'unica modifica.
Siamo pronti per eseguire i test. Configureremo il server web [Cassini] come abbiamo fatto nei test precedenti. Popoleremo la tabella articles con i seguenti valori:

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

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

Gli articoli [coltello] e [cucchiaio] sono stati acquistati e le loro giacenze sono state ridotte della quantità acquistata. L'articolo [forchetta] non ha potuto essere acquistato perché la quantità richiesta superava la quantità disponibile in magazzino. Invitiamo il lettore a eseguire ulteriori test.
2.5.6.9.2. Origine dati MSDE
Qui stiamo testando l'origine dati MSDE discussa nella sezione 2.4.3.1. Viene utilizzata qui 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 [providers.config, properties.xml] cambiano. I file di configurazione [sqlmap.config, articles.xml] rimangono invariati.
- 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 accedere alle origini dati di SQL Server. È incluso di default nel file modello [providers.config] distribuito con SqlMap.
- Il file [properties.xml] definisce il <provider> per l'origine 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 il test. Il server web [Cassini] mantiene la sua configurazione abituale. Inizializziamo la tabella degli articoli nel sorgente MSDE utilizzando [EMS MS SQL Manager]:

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

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

Gli articoli [pallone da calcio] e [racchetta da tennis] sono stati acquistati e le loro giacenze sono state ridotte della quantità acquistata. L'articolo [pattini a rotelle] non ha potuto essere acquistato perché la quantità richiesta superava la quantità disponibile in magazzino. Invitiamo il lettore a eseguire ulteriori test.
2.5.6.9.3. Origine dati OleDb
Qui stiamo testando l'origine dati ACCESS presentata nella sezione 2.4.5.1. Viene utilizzata qui 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 [providers.config, properties.xml] cambiano. I file di configurazione [sqlmap.config, articles.xml] rimangono invariati.
- 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 accedere alle origini dati OleDb. È incluso per impostazione predefinita nel file modello [providers.config] distribuito con SqlMap.
- Il file [properties.xml] definisce il <provider> della sorgente OleDb e la sua 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 il test. Il server web [Cassini] mantiene la sua configurazione abituale. Inizializziamo la tabella degli articoli dalla fonte ACCESS come segue:

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

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

Gli articoli [pants] e [skirt] sono stati acquistati e le loro scorte sono state ridotte della quantità acquistata. L'articolo [coat] non ha potuto essere acquistato perché la quantità richiesta superava quella disponibile in magazzino. Invitiamo il lettore a eseguire 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 per le sorgenti SQL Server
- utilizzando classi di accesso .NET per 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, dominio, DAO] senza ricompilare nessuno dei livelli [web, dominio]
- 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 al codice. Pertanto, con questo nuovo livello, siamo stati in grado di utilizzare successivamente le fonti di dati delle precedenti implementazioni da 1 a 3. Ciò è stato fatto in modo trasparente utilizzando file di configurazione.
- Abbiamo dimostrato la grande flessibilità che gli strumenti Spring e SqlMap apportano alle applicazioni web a tre livelli.































































