1. Introduzione a ORM NHibernate
Il PDF di questo documento è disponibile |QUI|.
Gli esempi del documento sono disponibili |QUI|.
Questo documento è una breve introduzione a NHibernate, l’equivalente per .NET del framework Java Hibernate. Per un’introduzione completa si può consultare:
Titolo: NHibernate in Action, Autore: Pierre-Henri Kuaté, Editore: Manning, ISBN-13: 978-1932394924
Un ORM (Object Relational Mapper) è un insieme di librerie che consente a un programma che utilizza un database di interagirvi senza emettere comandi SQL espliciti e senza conoscere le specificità del SGBD utilizzato.
Prerequisiti
In una struttura [débutant-intermédiaire-avancé], questo documento si trova nella sezione [intermédiaire]. Per comprenderlo sono necessari diversi prerequisiti che è possibile trovare in alcuni dei documenti che ho scritto:
- Linguaggio C# 2008: [Imparare C# versione 3.0 con .NET Framework 3.5 (2008)]
- [Spring IoC], disponibile all'URL [Spring IoC per .NET (2005)]. Presenta le basi dell’inversione di controllo (Inversion of Control) o dell’iniezione di dipendenze (Dependency Injection) del framework Spring.Net [Spring.NET | Homepage ].
All'inizio di alcuni paragrafi del presente documento vengono talvolta forniti suggerimenti di lettura che rimandano ai documenti precedenti.
Strumenti
Gli strumenti utilizzati in questo caso di studio sono disponibili gratuitamente sul web. Sono i seguenti (dicembre 2011):
- Nhibernate 3.2 disponibile all'URL [http://nhforge.org/Default.aspx]
- Spring.net 1.3.2 disponibile all'URL [http://www.springframework.net]. Il framework Spring.net è molto completo. In questa sede utilizzeremo solo la libreria che esso fornisce per facilitare l'uso del framework Nhibernate.
- Log4net 1.2.10 disponibile all'URL [http://logging.apache.org/log4net]. Questo framework di log viene utilizzato da Nhibernate.
- Nunit 2.5 è disponibile all’URL [http://www.nunit.org/]. Questo framework per i test unitari è l’equivalente per .NET del framework JUnit per la piattaforma Java.
- Il driver ADO.NET 6.4.4 di SGBD MySQL 5 è disponibile all'URL [http://dev.mysql.com/downloads/connector/net]
Tutti i file DLL necessari per i progetti Visual Studio 2010 sono stati raccolti in una cartella [libnet4]:
![]() |
1.1. Il ruolo di NHIBERNATE in un'architettura a livelli .NET
Un’applicazione .NET che utilizza un database può essere strutturata a livelli nel modo seguente:
![]() |
Il livello [dao] comunica con il SGBD tramite il API ADO.NET. Ricordiamo i principali metodi di questo API.
In modalità connessa, l’applicazione:
- apre una connessione con la fonte dati
- opera con la fonte dati in modalità lettura/scrittura
- chiude la connessione
Tre interfacce ADO.NET sono principalmente interessate da queste operazioni:
- IDbConnection, che incapsula le proprietà e i metodi della connessione.
- IDbCommand, che incapsula le proprietà e i metodi del comando SQL eseguito.
- IDataReader, che incapsula le proprietà e i metodi del risultato di un comando SQL Select.
L'interfaccia IDbConnection
serve a gestire la connessione con il database. Tra i metodi M e le proprietà P di questa interfaccia si trovano i seguenti:
Nome | Tipo | Ruolo |
P | Stringa di connessione al database. Specifica tutti i parametri necessari per stabilire la connessione con un database specifico. | |
M | apre la connessione con il database definito da ConnectionString | |
M | chiude la connessione | |
M | avvia una transazione. | |
P | stato della connessione: ConnectionState.Closed, ConnectionState.Open, ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken |
Se Connection è una classe che implementa l'interfaccia IDbConnection, l'apertura della connessione può avvenire come segue:
L'interfaccia IDbCommand
Serve per eseguire un comando SQL o una procedura memorizzata. Tra i metodi M e le proprietà P di questa interfaccia si trovano i seguenti:
Nome | Tipo | Ruolo |
P | indica cosa deve essere eseguito - prende i propri valori da un elenco: - CommandType.Text: esegue il comando SQL definito nella proprietà CommandText. È il valore predefinito. - CommandType.StoredProcedure: esegue una procedura memorizzata nel database | |
P | - il testo del comando SQL da eseguire se CommandType = CommandType.Text - il nome della procedura memorizzata da eseguire se CommandType = CommandType.StoredProcedure | |
P | la connessione IDbConnection da utilizzare per eseguire l'ordine SQL | |
P | la transazione IDbTransaction in cui eseguire l'ordine SQL | |
P | l'elenco dei parametri di un ordine SQL configurato. L'ordine update articles set price=price*1.1 where id=@id ha il parametro @id. | |
M | per eseguire un comando SQL Select. Si ottiene un oggetto IDataReader che rappresenta il risultato di Select. | |
M | per eseguire un comando SQL Aggiorna, Inserisci, Elimina. Si ottiene il numero di righe interessate dall'operazione (aggiornate, inserite, eliminate). | |
M | per eseguire un comando SQL. Select restituisce un unico risultato, come nel caso di: select count(*) from articles. | |
M | per creare i parametri IDbParameter di un ordine SQL configurato. | |
M | consente di ottimizzare l'esecuzione di una query parametrizzata quando viene eseguita più volte con parametri diversi. |
Se Command è una classe che implementa l'interfaccia IDbCommand, l'esecuzione di un comando SQL senza transazione avrà la seguente forma:
L'interfaccia IDataReader
serve a incapsulare i risultati di un comando SQL Select. Un oggetto IDataReader rappresenta una tabella con righe e colonne, che viene elaborata in modo sequenziale: prima la prima riga, poi la seconda, ... Tra i metodi M e le proprietà P di questa interfaccia si trovano i seguenti:
Nome | Tipo | Ruolo |
P | il numero di colonne della tabella IDataReader | |
M | GetName(i) restituisce il nome della colonna n. i della tabella IDataReader. | |
P | Item[i] rappresenta la colonna n. i della riga corrente della tabella IDataReader. | |
M | passa alla riga successiva della tabella IDataReader. Restituisce il valore booleano True se la lettura è andata a buon fine, False in caso contrario. | |
M | chiude la tabella IDataReader. | |
M | GetBoolean(i): restituisce il valore booleano della colonna n. i della riga corrente della tabella IDataReader. Gli altri metodi analoghi sono i seguenti: GetDateTime, GetDecimal, GetDouble, GetFloat, GetInt16, GetInt32, GetInt64, GetString. | |
M | Getvalue(i): restituisce il valore della colonna n. i della riga corrente della tabella IDataReader come tipo object. | |
M | IsDBNull(i) restituisce True se la colonna n. i della riga corrente della tabella IDataReader non ha alcun valore, il che è indicato dal valore SQL NULL. |
L'elaborazione di un oggetto IDataReader spesso si presenta come segue:
Nell'architettura precedente,
![]() |
il connettore [ADO.NET] è collegato a SGBD. Pertanto, la classe che implementa l'interfaccia [IDbConnection] è:
- la classe [MySQLConnection] per SGBD MySQL
- la classe [SQLConnection] per SGBD e SQLServer
Il livello [dao] dipende quindi dal SGBD utilizzato. Alcuni framework (Linq, Ibatis.net, NHibernate) eliminano questa limitazione aggiungendo un livello supplementare tra il livello [dao] e il connettore [ADO.NET] del SGBD utilizzato. In questo caso utilizzeremo il framework [NHibernate].
![]() |
Nell’esempio sopra riportato, il livello [dao] non si rivolge più al connettore [ADO.NET], ma al framework NHibernate, che gli fornirà un’interfaccia indipendente dal connettore [ADO.NET] utilizzato. Questa architettura consente di sostituire il connettore SGBD senza modificare il livello [dao]. In tal caso, dovrà essere sostituito solo il connettore [ADO.NET].
1.2. Il database di esempio
Per illustrare come lavorare con NHibernate, utilizzeremo il seguente database MySQL [dbpam_nhibernate]:
![]() |
- In [1], il database presenta tre tabelle:
- [employes]: una tabella che registra le dipendenti di un asilo nido
- [cotisations]: una tabella che registra le aliquote dei contributi sociali
- [indemnites]: una tabella che registra le informazioni necessarie per calcolare la busta paga delle dipendenti
Tabella [employes]
![]() |
- in [2], la tabella delle dipendenti e in [3], il significato dei suoi campi
Il contenuto della tabella potrebbe essere il seguente:
Tabella [cotisations]
![]() |
- in [4], la tabella dei contributi e in [5], il significato dei suoi campi
Il contenuto della tabella potrebbe essere il seguente:
Tabella [indemnites]
![]() |
- in [6], la tabella delle indennità e in [7], il significato dei suoi campi
Il contenuto della tabella potrebbe essere il seguente:
L’esportazione della struttura del database in un file SQL fornisce il seguente risultato:
Si noti, alle righe 6, 20 e 36, che le chiavi primarie ID hanno l'attributo autoincrement. Ciò significa che MySQL genererà automaticamente i valori delle chiavi primarie ogni volta che viene aggiunto un record. Lo sviluppatore non deve preoccuparsene.
1.3. Il progetto C# di dimostrazione
Per illustrare la configurazione e l’utilizzo di NHibernate, utilizzeremo la seguente architettura:
![]() |
Un programma da console [1] gestirà i dati del database precedente [2] tramite il framework [NHibernate] [3]. Questo ci porterà a presentare:
- i file di configurazione di NHibernate
- API derivato da NHibernate
Il progetto C# sarà il seguente:
![]() |
Gli elementi necessari per il progetto sono i seguenti:
- in [1], i file DLL necessari al progetto:
- [NHibernate]: il DLL del framework NHibernate
- [MySql.Data]: il DLL del connettore ADO.NET del SGBD MySQL
- [log4net]: il DLL del framework Log4net che consente di generare log
- in [2], le classi che rappresentano le tabelle del database
- in [3], il file [App.config] che configura l'intera applicazione, compreso il framework [NHibernate]
- in [4], le applicazioni console di test
1.3.1. Configurazione della connessione al database
Torniamo all’architettura di test:
![]() |
Come illustrato sopra, [NHibernate] deve poter accedere al database. A tal fine, necessita di alcune informazioni:
- il SGBD che gestisce il database (MySQL, SQLServer, Postgres, Oracle, ...). La maggior parte dei SGBD ha aggiunto al linguaggio SQL delle estensioni proprie. Conoscendo il SGBD, il NHibernate può adattare i comandi SQL che emette a tale SGBD. NHibernate utilizza il concetto di dialetto SQL.
- i parametri di connessione al database (nome del database, nome dell’utente titolare della connessione, relativa password)
Queste informazioni possono essere inserite nel file di configurazione [App.config]. Ecco quello che verrà utilizzato con un database MySQL 5:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- sezioni di configurazione -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- Configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
<!-- Questa sezione contiene le impostazioni di configurazione di log4net -->
<!-- NOTE IMPORTANTE: i log non sono attivi per impostazione predefinita. È necessario attivarli a livello di programma con l'istruzione log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<!-- Definire un appender di output (dove devono essere inviati i log) -->
<appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
<param name="File" value="log.txt" />
<param name="AppendToFile" value="false" />
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" />
</layout>
</appender>
<appender name="LogDebugAppender" type="log4net.Appender.DebugAppender, log4net">
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/>
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout, log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"/>
</layout>
</appender>
<!-- Configurare la categoria principale, impostare il livello di priorità predefinito e aggiungere gli appender (dove verranno inviati i log) -->
<root>
<priority value="INFO" />
<!--
<appender-ref ref="LogFileAppender" />
<appender-ref ref="LogDebugAppender"/>
-->
<appender-ref ref="ConsoleAppender"/>
</root>
<!-- Specificare il livello per alcuni spazi dei nomi specifici -->
<!-- Il livello può essere: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
<logger name="NHibernate">
<level value="INFO" />
</logger>
</log4net>
</configuration>
- righe 4-7: definiscono le sezioni di configurazione nel file [App.config]. Consideriamo la riga 6:
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
Questa riga definisce la sezione di configurazione di NHibernate nel file [App.config]. Presenta due attributi: name e type.
- L'attributo [name] identifica la sezione di configurazione. Tale sezione deve essere delimitata dai tag <name>...</name>, in questo caso <hibernate-configuration>...</hibernate-configuration>, nelle righe 11-24.
- L'attributo [type=classe,DLL] indica il nome della classe incaricata di gestire la sezione definita dall'attributo [name], nonché la DLL contenente tale classe. In questo caso, la classe si chiama [NHibernate.Cfg.ConfigurationSectionHandler] e si trova nella DLL [NHibernate.dll]. Ricordiamo che questa DLL fa parte dei riferimenti del progetto in esame.
Consideriamo ora la sezione di configurazione di NHibernate:
<!-- configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
- riga 2: la configurazione di NHibernate si trova all’interno di un tag <hibernate-configuration>. L’attributo xmlns (Xml NameSpace) specifica la versione utilizzata per configurare NHibernate. Infatti, nel corso del tempo, il modo di configurare NHibernate si è evoluto. In questo caso, viene utilizzata la versione 2.2.
- riga 3: la configurazione di NHibernate è qui interamente contenuta nel tag <session-factory> (righe 3 e 14). Una sessione NHibernate è lo strumento utilizzato per lavorare con un database secondo lo schema:
- apertura della sessione
- lavoro con il database tramite i metodi di API NHibernate
- chiusura della sessione
La sessione viene creata da un factory, un termine generico che indica una classe in grado di creare oggetti. Le righe da 3 a 14 configurano questo factory.
- righe 4, 6, 8, 9: configurano la connessione al database di destinazione. Le informazioni principali sono il nome del SGBD utilizzato, il nome del database, l’identità dell’utente e la sua password.
- riga 4: definisce il provider della connessione, ovvero quello a cui si richiede una connessione al database. Il valore della proprietà [connection.provider] è il nome di una classe NHibernate. Questa proprietà non dipende dal SGBD utilizzato.
- riga 6: il driver ADO.NET da utilizzare. Si tratta del nome di una classe NHibernate specializzata per un determinato SGBD, in questo caso MySQL. La riga 6 è stata commentata, poiché non è indispensabile.
- Riga 8: la proprietà [dialect] imposta il dialetto SQL da utilizzare con il SGBD. In questo caso si tratta del dialetto di SGBD MySQL.
Se si cambia il SGBD, come si individua il dialetto NHibernate corrispondente? Torniamo al progetto C# precedente e facciamo doppio clic su DLL [NHibernate] nella scheda [References]:
![]() |
- in [1], la scheda [Explorateur d'objets] mostra una serie di DLL, tra cui quelle a cui fa riferimento il progetto.
- in [2], la DLL [NHibernate]
- in [3], la DLL e la [NHibernate] sviluppata. Qui si trovano i diversi spazi dei nomi (namespace) che vi sono definiti.
- in [4], lo spazio dei nomi [NHibernate.Dialect] in cui si trovano le classi che definiscono i diversi dialetti SQL utilizzabili.
- in [5], la classe del dialetto di SGBD MySQL 5.
![]() |
- in [6], lo spazio dei nomi della classe [MySqlDataDriver] utilizzata alla riga 6 qui sotto:
<!-- configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQLDialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
- righe 9-11: la stringa di connessione al database. Questa stringa ha la forma "param1=val1;param2=val2; ...". L'insieme dei parametri così definiti consente al driver di SGBD di stabilire una connessione. La struttura di questa stringa di connessione dipende dal SGBD utilizzato. Le stringhe di connessione ai principali SGBD sono disponibili sul sito [http://www.connectionstrings.com/]. In questo caso, la stringa "Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;" è una stringa di connessione per il SGBD MySQL. Essa indica che:
- Server=localhost;: il SGBD si trova sulla stessa macchina del client che sta tentando di aprire la connessione
- Database=dbpam_nhibernate; : il database MySQL di destinazione
- Uid=root; : l'utente che apre la connessione è l'utente root
- Pwd=;: questo utente non ha una password (caso particolare di questo esempio)
- riga 12: la proprietà [show_sql] indica se NHibernate deve visualizzare nei propri log i comandi SQL che invia al database. In fase di sviluppo, è utile impostare questa proprietà su [true] per sapere esattamente cosa fa NHibernate.
- riga 13: per comprendere il tag <mapping>, torniamo all’architettura dell’applicazione:
![]() |
Se il programma da console fosse un client diretto del connettore ADO.NET e volesse l’elenco dei dipendenti, farebbe eseguire al connettore un comando SQL Select, e riceverebbe in risposta un oggetto di tipo IDataReader che dovrebbe elaborare per ottenere l'elenco dei dipendenti inizialmente richiesto.
Nell’esempio sopra riportato, il programma da console è il client di NHibernate, mentre NHibernate è il client del connettore ADO.NET. Vedremo in seguito che il API di NHibernate consentirà al programma da console di richiedere l’elenco dei dipendenti. NHibernate tradurrà questa richiesta in un comando SQL Select che farà eseguire al connettore ADO.NET. Quest’ultimo restituirà un oggetto di tipo IDataReader. A partire da questo oggetto, NHibernate deve essere in grado di costruire l’elenco dei dipendenti che gli è stato richiesto. Ciò è reso possibile dalla configurazione. A ogni tabella del database è associata una classe C#. Pertanto, a partire dalle righe della tabella [employes] restituite da IDataReader, NHibernate sarà in grado di costruire un elenco di oggetti che rappresentano i dipendenti e di restituirlo al programma console. Queste relazioni tra tabelle e classi vengono create nei file di configurazione. NHibernate utilizza il termine «mapping» per definire tali relazioni.
Torniamo alla riga 13 qui sotto:
<!-- configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
La riga 13 indica che i file di configurazione tabelle <--> classi si trovano nell’assembly [pam-nhibernate-demos]. Un assembly è l’eseguibile o il DLL generato dalla compilazione di un progetto. In questo caso, i file di mappatura saranno inseriti nell’assembly del progetto di esempio. Per conoscere il nome di questo assembly, è necessario consultare le proprietà del progetto:
![]() |
- in [1], le proprietà del progetto
- nella scheda [Application] [2], il nome dell'assembly [3] che verrà generato.
- Poiché il tipo di output è [Application console] [4], il file generato durante la compilazione del progetto si chiamerà [pam-nhibernate-demos.exe]. Se il tipo di output fosse [Bibliothèque de classes] [5], il file generato durante la compilazione del progetto si chiamerebbe [pam-nhibernate-demos.dll]
- l'assembly viene generato nella cartella [bin/Release] del progetto [6].
Dalla spiegazione precedente si evince che i file delle tabelle di mappatura <--> classi dovranno trovarsi nel file [pam-nhibernate-demos.exe] [6].
1.3.2. Configurazione del file di mappatura " " <--> classi
Torniamo all’architettura del progetto in esame:
![]() |
- nel file [1] il programma da console utilizza i metodi del file API del framework NHibernate. Questi due blocchi si scambiano oggetti.
- In [2], NHibernate utilizza API di un connettore .NET. Invia comandi SQL al SGBD di destinazione.
Il programma da console gestirà oggetti che rispecchiano le tabelle del database. In questo progetto, tali oggetti e i collegamenti che li legano alle tabelle del database sono stati inseriti nella cartella [Entites] riportata di seguito:
![]() |
- Ogni tabella del database è rappresentata da una classe e da un file di mappatura tra le due
Tabella | Classe | Mappatura |
contributi | Cotisations.cs | Cotisations.hbm.xml |
dipendenti | Employe.cs | Employe.hbm.xml |
indennità | Indemnites.cs | Indemnites.hbm.xml |
1.3.2.1. Mappatura della tabella [cotisations]
Consideriamo la tabella [cotisations]:
![]() |
|
Una riga di questa tabella può essere incapsulata in un oggetto di tipo [Cotisations.cs] come segue:
namespace PamNHibernateDemos {
public class Cotisations {
// proprietà automatiche
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual double CsgRds { get; set; }
public virtual double Csgd { get; set; }
public virtual double Secu { get; set; }
public virtual double Retraite { get; set; }
// costruttori
public Cotisations() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
È stata creata una proprietà automatica per ciascuna delle colonne della tabella [cotisations]. Ciascuna di queste proprietà deve essere dichiarata virtuale (virtual) poiché NHibernate è destinata a derivare dalla classe e a ridefinirne (override) le proprietà. Queste devono quindi essere virtuali.
Si noti, alla riga 1, che la classe appartiene allo spazio dei nomi [PamNHibernateDemos].
Il file di mappatura [Cotisations.hbm.xml] tra la tabella [cotisations] e la classe [Cotisations] è il seguente:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Cotisations" table="COTISATIONS">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="CsgRds" column="CSGRDS"/>
<property name="Csgd" column="CSGD"/>
<property name="Retraite" column="RETRAITE"/>
<property name="Secu" column="SECU"/>
</class>
</hibernate-mapping>
- il file di mappatura è un file XML definito all’interno del tag <hibernate-mapping> (righe 2 e 14)
- riga 4: il tag <class> collega una tabella del database a una classe. In questo caso, la tabella [COTISATIONS] (attributo table) e la classe [Cotisations] (attributo name). In .NET, una classe deve essere definita tramite il suo nome completo (spazio dei nomi incluso) e tramite l’assembly che la contiene. Queste due informazioni sono fornite dalla riga 3. La prima (spazio dei nomi) può essere reperita nella definizione della classe. La seconda (assembly) è il nome dell’assembly del progetto. Abbiamo già indicato come trovare questo nome.
- righe 5-7: il tag <id> serve a definire la mappatura della chiave primaria della tabella [cotisations].
- riga 5: l’attributo name indica il campo della classe [Cotisations] che riceverà la chiave primaria della tabella [cotisations]. L'attributo column indica la colonna della tabella [cotisations] che funge da chiave primaria. L'attributo unsaved-value serve a definire una chiave primaria non ancora generata. Questo valore consente a NHibernate di sapere come salvare un oggetto [Cotisations] nella tabella [cotisations]. Se tale oggetto ha un campo Id=0, verrà eseguita un'operazione SQL INSERT; in caso contrario, verrà eseguita un'operazione SQL UPDATE. Il valore di unsaved-value dipende dal tipo del campo Id della classe [Cotisations]. In questo caso, è di tipo int e il valore predefinito di un tipo int è 0. Un oggetto [Cotisations] non ancora salvato (quindi privo di chiave primaria) avrà quindi il campo Id pari a 0. Se il campo Id fosse stato di tipo Object o derivato, si sarebbe scritto unsaved-value=null.
- riga 6: quando NHibernate deve salvare un oggetto [Cotisations] con un campo Id=0, deve eseguire sul database un'operazione INSERT durante la quale deve ottenere un valore per la chiave primaria del record. La maggior parte dei SGBD dispone di un metodo proprietario per generare automaticamente tale valore. Il tag <generator> serve a definire il meccanismo da utilizzare per la generazione della chiave primaria. Il tag <generator class="native"> indica che occorre utilizzare il meccanismo predefinito del SGBD utilizzato. Abbiamo visto nel paragrafo 1.2 che le chiavi primarie delle nostre tre tabelle MySQL avevano l’attributo autoincrement. Durante le operazioni INSERT, NHibernate non fornirà alcun valore alla colonna ID del record aggiunto, lasciando che sia MySQL a generare tale valore.
- riga 8: il tag <version> serve a definire la colonna della tabella (nonché il campo della classe corrispondente) che consente di “versionarizzare” i record. Inizialmente, la versione è pari a 1. Viene incrementata ad ogni operazione UPDATE. D’altra parte, ogni operazione UPDATE o DELETE viene eseguita con un filtro WHERE ID= id AND VERSION=v1. Un utente può quindi modificare o eliminare un oggetto solo se ne possiede la versione corretta. In caso contrario, viene generata un'eccezione da NHibernate.
- riga 9: il tag <property> serve a definire una mappatura di colonna normale (né chiave primaria, né colonna di versione). Pertanto, la riga 9 indica che la colonna CSGRDS della tabella [COTISATIONS] è associata alla proprietà CsgRds della classe [Cotisations].
1.3.2.2. Mappatura della tabella [indemnites]
Consideriamo la tabella [indemnites]:
![]() |
|
Una riga di questa tabella può essere incapsulata in un oggetto di tipo [Indemnites] come segue:
namespace PamNHibernateDemos {
public class Indemnites {
// proprietà automatiche
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual int Indice { get; set; }
public virtual double BaseHeure { get; set; }
public virtual double EntretienJour { get; set; }
public virtual double RepasJour { get; set; }
public virtual double IndemnitesCp { get; set; }
// costruttori
public Indemnites() {
}
// identità
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
Il file di mappatura della tabella [indemnites] <--> classe [Indemnites] potrebbe essere il seguente (Indemnites.hbm.xml):
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Indemnites" table="INDEMNITES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="Indice" column="INDICE" unique="true"/>
<property name="BaseHeure" column="BASE_HEURE" />
<property name="EntretienJour" column="ENTRETIEN_JOUR" />
<property name="RepasJour" column="REPAS_JOUR" />
<property name="IndemnitesCp" column="INDEMNITES_CP" />
</class>
</hibernate-mapping>
Non vi è nulla di nuovo rispetto al file di mappatura spiegato in precedenza. L’unica differenza si trova alla riga 9. L'attributo unique="true" indica che nella tabella [indemnites] è presente un vincolo di unicità sulla colonna [INDICE]: non possono esserci due righe con lo stesso valore per la colonna [INDICE].
1.3.2.3. Mappatura della tabella [employes]
Consideriamo la tabella [employes]:
![]() |
|
La novità rispetto alle tabelle precedenti è la presenza di una chiave esterna: la colonna [INDEMNITE_ID] è una chiave esterna sulla colonna [ID] della tabella [INDEMNITES]. Questo campo fa riferimento alla riga della tabella [INDEMNITES] da utilizzare per il calcolo delle indennità del dipendente.
La classe [Employe] immagine della tabella [employes] potrebbe essere la seguente:
namespace PamNHibernateDemos {
public class Employe {
// proprietà automatiche
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string SS { get; set; }
public virtual string Nom { get; set; }
public virtual string Prenom { get; set; }
public virtual string Adresse { get; set; }
public virtual string Ville { get; set; }
public virtual string CodePostal { get; set; }
public virtual Indemnites Indemnites { get; set; }
// costruttori
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
Il file di mappatura [Employe.hbm.xml] potrebbe essere il seguente:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS"/>
<property name="Nom" column="NOM"/>
<property name="Prenom" column="PRENOM"/>
<property name="Adresse" column="ADRESSE"/>
<property name="Ville" column="VILLE"/>
<property name="CodePostal" column="CP"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
</class>
</hibernate-mapping>
La novità si trova alla riga 15 con la comparsa di un nuovo tag: <many-to-one>. Questo tag serve a mappare una colonna chiave esterna [INDEMNITE_ID] della tabella [EMPLOYES] alla proprietà [Indemnites] della classe [Employe]:
namespace PamNHibernateDemos {
public class Employe {
// proprietà automatiche
..
public virtual Indemnites Indemnites { get; set; }
...
}
}
La tabella [EMPLOYES] ha una chiave esterna [INDEMNITE_ID] che fa riferimento alla colonna [ID] della tabella [INDEMNITES]. Diverse (many) righe della tabella [EMPLOYES] possono fare riferimento a una sola (one) riga della tabella [INDEMNITES]. Da qui il nome del tag <many-to-one>. Questo tag presenta qui i seguenti attributi:
- column: indica il nome della colonna della tabella [EMPLOYES] che funge da chiave esterna nella tabella [INDEMNITES]
- name: indica la proprietà della classe [Employe] associata a questa colonna. Il tipo di questa proprietà è necessariamente la classe associata alla tabella di destinazione della chiave esterna, in questo caso la tabella [INDEMNITES]. Sappiamo che questa classe è la classe [Indemnites] già descritta. Questo è quanto riflette la riga 5 sopra riportata. Ciò significa che quando NHibernate recupererà dal database un oggetto [Employe], recupererà anche l’oggetto [Indemnites] ad esso associato.
- cascata: questo attributo può assumere diversi valori:
- save-update: un'operazione di inserimento (save) o di aggiornamento (update) sull'oggetto [Employe] deve essere propagata all'oggetto [Indemnites] in esso contenuto.
- delete: l'eliminazione di un oggetto [Employe] deve essere propagata all'oggetto [Indemnites] che contiene.
- all: propaga le operazioni di inserimento (save), aggiornamento (update) ed eliminazione (delete).
- none: non propaga nulla
Per concludere, ricordiamo la configurazione di NHibernate nel file [App.config]:
<!-- configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
La riga 13 indica che i file di mappatura *.hbm.xml si troveranno nell’assembly [pam-nhibernate-demos]. Questa impostazione non è predefinita. È necessario configurarla nel progetto C#:
![]() |
- in [1], si selezionano le proprietà di un file di mappatura
- in [2], l'azione di generazione deve essere [Ressource incorporée] [3]. Ciò significa che, al momento della generazione del progetto, il file di mappatura deve essere incorporato nell'assembly generato.
1.4. da API a NHibernate
Torniamo all’architettura del nostro progetto di esempio:
![]() |
Nei paragrafi precedenti abbiamo configurato NHibernate in due modi:
- in [App.config] abbiamo configurato la connessione al database
- abbiamo specificato, per ogni tabella del database, la classe che la rappresenta e il file di mappatura che consente di passare dalla classe alla tabella e viceversa.
Non ci resta che scoprire i metodi offerti da NHibernate per gestire i dati del database: inserimento, aggiornamento, cancellazione, elenco.
1.4.1. L’oggetto SessionFactory
Ogni operazione NHibernate viene eseguita all’interno di una sessione. Una sequenza tipica di operazioni NHibernate è la seguente:
- aprire una sessione NHibernate
- avviare una transazione nella sessione
- eseguire operazioni di persistenza con la sessione (Load, Get, Find, CreateQuery, Save, SaveOrUpdate, Delete)
- convalidare (commit) o annullare (rollback) la transazione
- chiudere la sessione NHibernate
Una sessione viene ottenuta da una factory di tipo [SessionFactory]. Questa factory è quella configurata dal tag <session-factory> nel file di configurazione [App.config]:
<!-- configurazione NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-nhibernate-demos"/>
</session-factory>
</hibernate-configuration>
In un codice C#, la SessionFactory può essere ottenuta nel modo seguente:
ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
La classe Configuration è una classe del framework NHibernate. L'istruzione precedente utilizza la sezione di configurazione di NHibernate in [App.config]. L'oggetto [ISessionFactory] ottenuto contiene quindi le:
- le informazioni necessarie per creare una connessione al database di destinazione
- file di mappatura tra le tabelle del database e le classi persistenti gestite da NHibernate.
1.4.2. La sessione NHibernate
Una volta creato SessionFactory (operazione che va eseguita una sola volta), è possibile ottenere le sessioni che consentono di eseguire operazioni di persistenza NHibernate. Un codice tipico è il seguente:
try{
// accesso
using (ISession session = sessionFactory.OpenSession())
{
// inizio transazione
using (ITransaction transaction = session.BeginTransaction())
{
........................ opérations de persistance
// convalida della transazione
transaction.Commit();
}
}
}catch (Exception ex){
....
}
- riga 3: viene creata una sessione a partire da SessionFactory all’interno di una clausola using. All'uscita dalla clausola using, la sessione verrà chiusa automaticamente. Senza la clausola using, sarebbe necessario chiudere la sessione in modo esplicito (session.Close()).
- riga 6: le operazioni di persistenza verranno eseguite all’interno di una transazione. O vanno a buon fine tutte, oppure nessuna va a buon fine. All’interno della clausola using, la transazione viene convalidata da un Commit (riga 10). Se all’interno della transazione un’operazione di persistenza genera un’eccezione, la transazione verrà automaticamente invalidata da un Rollback all’uscita dal using.
- Il blocco try/catch delle righe 1 e 13 consente di intercettare un'eventuale eccezione generata dal codice all'interno del blocco try (sessione, transazione, persistenza).
1.4.3. L'interfaccia ISession
Presentiamo ora alcuni dei metodi dell’interfaccia ISession implementata da una sessione NHibernate:
avvia una transazione nella sessione ITransaction tx=session.BeginTransaction(); | |
svuota la sessione. Gli oggetti in essa contenuti diventano distaccati. session.Clear(); | |
chiude la sessione. Gli oggetti in essa contenuti vengono sincronizzati con il database. Questa operazione di sincronizzazione viene eseguita anche al termine di una transazione. Quest'ultimo caso è il più comune. session.Close(); | |
crea una query HQL (Hibernate Query Language) per un'esecuzione successiva. IQuery query=session.createQuery("select e from Employe e); | |
elimina un oggetto. Quest'ultimo può appartenere alla sessione (associato) oppure no (non associato). Durante la sincronizzazione della sessione con il database, su questo oggetto verrà eseguita un'operazione SQL DELETE. // si carica un dipendente da BD Dipendente e = session.Get<Dipendente>(143); // lo si elimina session.Delete(e); | |
forza la sincronizzazione della sessione con il database. Il contenuto della sessione non cambia. session.Flush(); | |
recupera dal database l'oggetto T con chiave primaria id. Se tale oggetto non esiste, imposta il puntatore a null. // si carica un dipendente da BD Dipendente e = session.Get<Dipendente>(143); | |
inserisce l'oggetto obj nella sessione. Questo oggetto non ha una chiave primaria prima del Save. Dopo il Save, ne ha una. Durante la sincronizzazione della sessione, verrà eseguita un'operazione SQL INSERT sul database. // si crea un dipendente Dipendente e = new Dipendente(){...}; // lo si salva e = session.Save(e); | |
esegue l'operazione Save se obj non ha una chiave primaria oppure l'operazione Update se ne ha già una. | |
aggiorna l'oggetto obj nel database. Viene quindi eseguita un'operazione SQL UPDATE sul database. // si carica un dipendente da BD Dipendente e = session.Get<Dipendente>(143); // si modifica il suo nome e.Nom = ...; // lo si aggiorna nel database session.Update(e); |
1.4.4. L'interfaccia IQuery
L'interfaccia IQuery consente di eseguire query sul database per estrarne i dati. Abbiamo visto come crearne un'istanza:
Il parametro del metodo createQuery è una query HQL (Hibernate Query Language), un linguaggio analogo al linguaggio SQL ma che interroga le classi anziché le tabelle. La query sopra riportata richiede l'elenco di tutti i dipendenti. Ecco alcuni esempi di query HQL:
select e from Employe e where e.Nom like 'A%'
select e from Employe order by e.Nom asc
select e from Employe e where e.Indemnites.Indice=2
Presentiamo ora alcuni dei metodi dell’interfaccia IQuery:
restituisce il risultato della query sotto forma di un elenco di oggetti T IList<Dipendente> dipendenti = session.createQuery("select e from Dipendente e order by e.Nom asc").List<Dipendente>(); | |
restituisce il risultato della query sotto forma di un elenco in cui ogni elemento dell'elenco rappresenta una riga del risultato della query Select sotto forma di un array di oggetti. IList linee = session.createQuery("select e.Nom, e.Prenom, e.SS from Employe e").List(); righe[i][j] rappresenta la colonna j della riga i in un tipo object. Pertanto, righe[10][1] è un tipo object che rappresenta il nome di una persona. In genere sono necessarie conversioni di tipo per recuperare i dati nel loro tipo esatto. | |
restituisce il primo oggetto del risultato della query Employe e=session.createQuery("select e from Employe e where e.Nom='MARTIN'").UniqueResult<Employe>(); |
È possibile configurare una query HQL:
Nella query HQL alla riga 3, :num è un parametro che deve ricevere un valore prima che la query venga eseguita. Nell’esempio sopra riportato, a tale scopo viene utilizzato il metodo SetString. L’interfaccia IQuery dispone di vari metodi Set per assegnare un valore a un parametro:
- - SetBoolean(string name, bool value)
- - SetSingle(nome stringa, valore singolo)
- - SetDouble(nome stringa, valore double)
- - SetInt32(nome stringa, valore int32)
- ..
1.5. Alcuni esempi di codice
Gli esempi che seguono si basano sull'architettura analizzata in precedenza e riportata di seguito. Il database è il database MySQL [dbpam_nhibernate], anch'esso presentato. Gli esempi sono programmi da console [1] che utilizzano il framework NHibernate [3] per gestire il database [2].
![]() |
Il progetto C# in cui sono inseriti gli esempi che seguiranno è quello già presentato:
![]() |
- in [1], i file DLL necessari al progetto:
- [NHibernate]: il DLL del framework NHibernate
- [MySql.Data]: il DLL del connettore ADO.NET del SGBD MySQL 5
- [log4net]: il DLL di uno strumento che consente di generare log
- in [2], le classi delle immagini delle tabelle del database
- in [3], il file [App.config] che configura l'intera applicazione, compreso il framework [NHibernate]
- in [4], e le applicazioni console di test. Sono proprio queste ultime che presenteremo in parte.
1.5.1. Ottenere il contenuto del database
Il programma [ShowDataBase.cs] consente di visualizzare il contenuto del database:
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
namespace PamNHibernateDemos
{
public class ShowDataBase
{
private static ISessionFactory sessionFactory = null;
// programma principale
static void Main(string[] args)
{
// inizializzazione factory NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// visualizzazione del contenuto del database
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase1();
}
catch (Exception ex)
{
// visualizzazione dell'eccezione
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// attesa input da tastiera
Console.ReadLine();
}
// test1
static void ShowDataBase1()
{
// apertura sessione
using (ISession session = sessionFactory.OpenSession())
{
// inizio transazione
using (ITransaction transaction = session.BeginTransaction())
{
// si recupera l'elenco dei dipendenti
IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
// visualizzazione
Console.WriteLine("--------------- liste des employés");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// si recupera l'elenco delle indennità
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
// visualizzazione
Console.WriteLine("--------------- liste des indemnités");
foreach (Indemnites i in indemnites)
{
Console.WriteLine(i);
}
// si recupera l'elenco dei contributi
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- tableau des taux de cotisations");
Console.WriteLine(cotisations);
// conferma transazione
transaction.Commit();
}
}
}
}
}
Spiegazioni:
- riga 19: viene creato l’oggetto SessionFactory. È proprio questo che ci consentirà di ottenere gli oggetti Session.
- riga 24: si visualizza il contenuto del database
- righe 31-37: l'oggetto SessionFactory viene chiuso nella clausola finally dell'oggetto try.
- riga 43: il metodo che visualizza il contenuto del database
- riga 46: si ottiene una Session dalla SessionFactory.
- riga 49: si avvia una transazione
- riga 52: query HQL per recuperare l'elenco dei dipendenti. Grazie alla chiave esterna che collega l'entità Employe all'entità Indemnite, per ogni dipendente si otterrà la relativa indennità.
- riga 60: query HQL per ottenere l'elenco delle indennità.
- riga 68: query HQL per ottenere l'unica riga della tabella dei contributi.
- riga 72: fine della transazione
- riga 73: fine dell'istruzione «using Itransaction» della riga 49 – la transazione viene chiusa automaticamente
- riga 74: fine dell'using Isession della riga 46 – la sessione viene chiusa automaticamente.
Risultato visualizzato sullo schermo:
Si noti nelle righe 3 e 4 che, richiedendo un dipendente, è stata ottenuta anche la sua indennità.
1.5.2. Inserimento dei dati nel database
Il programma [FillDataBase.cs] consente di inserire dati nel database:
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
namespace PamNHibernateDemos
{
public class FillDataBase
{
private static ISessionFactory sessionFactory = null;
// programma principale
static void Main(string[] args)
{
// inizializzazione della factory NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// cancellazione del contenuto del database
Console.WriteLine("Effacement base -------------------------------------");
ClearDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
Console.WriteLine("Remplissage base -------------------------------------");
FillDataBase1();
Console.WriteLine("Affichage base -------------------------------------");
ShowDataBase();
}
catch (Exception ex)
{
// visualizzazione dell'eccezione
Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
}
finally
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
// attesa input da tastiera
Console.ReadLine();
}
// test1
static void ShowDataBase()
{
// vedi esempio precedente
}
// ClearDataBase1
static void ClearDataBase1()
{
// accesso
using (ISession session = sessionFactory.OpenSession())
{
// inizio transazione
using (ITransaction transaction = session.BeginTransaction())
{
// si recupera l'elenco dei dipendenti
IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
// si eliminano tutti i dipendenti
Console.WriteLine("--------------- suppression des employés associés");
foreach (Employe e in employes)
{
session.Delete(e);
}
// si recupera l'elenco delle indennità
IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
// si eliminano le indennità
Console.WriteLine("--------------- suppression des indemnités");
foreach (Indemnites i in indemnites)
{
session.Delete(i);
}
// si recupera l'elenco dei contributi
Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
Console.WriteLine("--------------- suppression des taux de cotisations");
if (cotisations != null)
{
session.Delete(cotisations);
}
// conferma transazione
transaction.Commit();
}
}
}
// FillDataBase
static void FillDataBase1()
{
// apertura sessione
using (ISession session = sessionFactory.OpenSession())
{
// inizio transazione
using (ITransaction transaction = session.BeginTransaction())
{
// si creano due indennità
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 };
// si creano due dipendenti
Employe e1 = new Employe() { Id = 0, SS = "254104940426058", Nom = "Jouveinal", Prenom = "Marie", Adresse = "5 rue des oiseaux", Ville = "St Corentin", CodePostal = "49203", Indemnites = i1 };
Employe e2 = new Employe() { Id = 0, SS = "260124402111742", Nom = "Laverti", Prenom = "Justine", Adresse = "La Brûlerie", Ville = "St Marcel", CodePostal = "49014", Indemnites = i2 };
// si creano le aliquote contributive
Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
// si salva il tutto
session.Save(e1);
session.Save(e2);
session.Save(cotisations);
// conferma transazione
transaction.Commit();
}
}
}
}
}
Spiegazioni
- riga 19: viene creato il SessionFactory
- righe 37-43: viene chiuso nella clausola finally del try
- riga 55: il metodo ClearDataBase1 che svuota il database. Il principio è il seguente:
- si recuperano tutti i dipendenti (riga 64) in un elenco
- li si eliminano uno per uno (righe 67-70)
- riga 93: il metodo FillDataBase1 inserisce alcuni dati nel database
- si creano due entità Indemnites (righe 102, 103)
- si creano due dipendenti con tali indennità (righe 105, 106)
- si crea un oggetto Cotisations alla riga 108.
- righe 110, 111: le due entità «Dipendente» vengono salvate nel database
- riga 112: a sua volta, l’entità «Contributi» viene salvata nel database
- Ci si potrebbe stupire del fatto che le entità Indemnités delle righe 102 e 103 non siano state salvate. In realtà sono state salvate contemporaneamente alle entità Employe. Per capirlo, occorre tornare alla mappatura dell’entità Employe:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID" unsaved-value="0">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS"/>
<property name="Nom" column="NOM"/>
<property name="Prenom" column="PRENOM"/>
<property name="Adresse" column="ADRESSE"/>
<property name="Ville" column="VILLE"/>
<property name="CodePostal" column="CP"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
</class>
</hibernate-mapping>
La riga 15, che mappa la relazione di chiave esterna dall’entità Employe all’entità Indemnites, presenta l’attributo cascade="save-update", il che comporta che le operazioni "save " e " update " dell'entità Employe vengano propagate all'entità interna Indemnites.
Schermata ottenuta:
Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations
Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]
1.5.3. Ricerca di un dipendente
Il programma [Program.cs] offre diversi metodi che illustrano l'accesso e la gestione dei dati del database. Ne presentiamo alcuni.
Il metodo [FindEmployee] consente di trovare un dipendente in base al suo numero di previdenza sociale:
// FindEmployee
static void FindEmployee() {
try {
// si avvia la sessione
using (ISession session = sessionFactory.OpenSession()) {
// inizio transazione
using (ITransaction transaction = session.BeginTransaction()) {
// ricerca di un dipendente in base al suo numero SS
String numSecu = "254104940426058";
IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
numSecu = "xx";
employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
if (employe != null) {
Console.WriteLine("Employe[" + numSecu + "]=" + employe);
} else {
Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
}
// conferma transazione
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Spiegazioni
- riga 10: la query Select configurata da numSecu da eseguire
- riga 11: l'assegnazione di un valore al parametro numSecu e l'esecuzione del metodo UniqueResult per ottenere un unico risultato.
Schermata ottenuta:
Recherche d'un employé -------------------------------------
Employe[254104940426058]=[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
Employe[xx] non trouvé...
1.5.4. Inserimento di entità non valide
Il metodo seguente tenta di salvare un'entità [Employe] non inizializzata.
// SaveEmptyEmployee
static void SaveEmptyEmployee() {
try {
// accesso
using (ISession session = sessionFactory.OpenSession()) {
// inizio transazione
using (ITransaction transaction = session.BeginTransaction()) {
// si crea un dipendente vuoto
Employe e = new Employe();
// si crea un'indennità inesistente
Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// che si associa al dipendente
e.Indemnites = i;
// si salva il dipendente lasciando vuoti gli altri campi
session.Save(e);
// si esegue il commit della transazione
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Spiegazioni
Ricordiamo il codice della classe [Employe]:
namespace PamNHibernateDemos {
public class Employe {
// proprietà automatiche
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string SS { get; set; }
public virtual string Nom { get; set; }
public virtual string Prenom { get; set; }
public virtual string Adresse { get; set; }
public virtual string Ville { get; set; }
public virtual string CodePostal { get; set; }
public virtual Indemnites Indemnites { get; set; }
// costruttori
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
Un oggetto [Employe] non inizializzato avrà il valore null per tutti i suoi campi di tipo stringa. Durante l'inserimento del record nella tabella [employes], NHibernate lascerà vuote le colonne corrispondenti a questi campi. Tuttavia, nella tabella [employes] tutte le colonne hanno l’attributo not null, il che impedisce la presenza di colonne prive di valore. Il driver ADO.NET genererà quindi un’eccezione:
1.5.5. Creazione di due indennità con lo stesso indice all’interno di una transazione
Nella tabella [indemnites], la colonna [indice] è stata dichiarata con l’attributo «unique», il che impedisce di avere due righe con lo stesso indice. Il metodo seguente crea due indennità con lo stesso indice all’interno di una transazione:
// CreateIndemnites1
static void CreateIndemnites1() {
try {
// accesso
using (ISession session = sessionFactory.OpenSession()) {
// inizio transazione
using (ITransaction transaction = session.BeginTransaction()) {
// si creano due indennità con lo stesso indice
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// si salvano
session.Save(i1);
session.Save(i2);
// conferma transazione
transaction.Commit();
}
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Spiegazioni
- nelle righe 9 e 10 si creano due entità Indemnites con lo stesso indice. Tuttavia, nel database, la colonna INDICE ha l’attributo UNIQUE.
- Le righe 12 e 13 inseriscono le due entità Indemnites nel contesto di persistenza. Quest’ultimo viene sincronizzato con il database al momento della convalida della transazione alla riga 15. Tale sincronizzazione genererà due INSERT. Il secondo genererà un'eccezione a causa dell'unicità della colonna INDICE. Poiché ci si trova all'interno di una transazione, il primo INSERT verrà annullato.
Il risultato ottenuto è il seguente:
Alla riga 9 si può notare che la tabella [indemnites] è vuota. Non è stato effettuato alcun inserimento.
1.5.6. Creazione di due indennità con lo stesso indice al di fuori della transazione
Il metodo seguente crea due indennità con lo stesso indice senza utilizzare alcuna transazione:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// accesso
using (ISession session = sessionFactory.OpenSession()) {
// si creano due indennità con lo stesso indice
Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.94, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
// si salvano
session.Save(i1);
session.Save(i2);
}
} catch (Exception e) {
Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
}
}
Spiegazioni
- Abbiamo lo stesso codice di prima, ma senza transazione.
- La sincronizzazione del contesto di persistenza con il database avverrà alla chiusura di tale contesto, riga 13 (chiusura del Session). La sincronizzazione genererà due INSERT. Il secondo fallirà a causa dell'unicità della colonna INDICE. Tuttavia, poiché non ci si trova all'interno di una transazione, il primo INSERT non verrà annullato.
Il risultato ottenuto è il seguente:
Il database era vuoto prima dell’esecuzione del metodo. Alla riga 6 si può notare che la tabella [indemnites] contiene una riga.























