Seconda ed penultima parte
Dopo aver
introdotto ieri le classi coinvolte, e qualche altra disgressione nei commenti
, vediamo oggi come realizzare un file di
mapping che possa fornire all'engine di persistenza di NHibernate tutte le
informazioni necessarie per salvare, caricare e cancellare un oggetto dal
database.
Innanzitutto, e ci tengo a dirlo, è da sfatare il mito secondo il quale
bisogna creare un file di mapping per ogni classe che
vogliamo gestire tramite NHibernate. In realtà, e lo vedremo
adesso, possiamo tranquillamente avere tutto dentro un solo file con estensione
hbm.xml e scrivere lì tutto il necessario. Magari, cosa
migliore se abbiamo a che fare con un numero considerevole di
classi, creare x files di mapping raggruppandole logicamente. Nel mio
esempio ho creato un solo file Mappings.hbm.xml, nel quale ho
inserito le informazioni relative alle due classi HockeyPlayer
e Faults.
<?xml version='1.0' encoding='utf-8'?>
<hibernate-mapping
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:xsd='http://www.w3.org/2001/XMLSchema'
namespace='HockeyObject'
assembly='HockeyObject' xmlns='urn:nhibernate-mapping-2.0'
default-access="property">
<class name='HockeyPlayer' table='HockeyPlayers'>
informazioni sul mapping
</class>
<class name='Fault' table='Faults'>
informazioni sul mapping
</class>
</hibernate-mapping>
Questo è lo scheletro intorno al quale ho costruito il mio file di
mapping. Non sto a dilungarmi troppo. Il tag root è <hibernate-mapping>, che stabilisce il
namespace ed il nome dell'assembly all'interno del quale sono definite le classi
che vogliamo persistere. Poi ho due tag <class>, uno per la classe
HockeyPlayer ed un altro per la classe Fault.
Ogni tag ha due proprietà: name (che
indica il nome della classe) e table
(che indica il nome della tabella che conterrà le istanze di HockeyPlayer).
Adesso svisceriamo ciascuna delle due classi e vediamo un po' come funziona la
cosa.
Il mapping per la classe HockeyPlayer
All'interno del tag
<class>, dobbiamo innanzitutto
dire ad NHibernate qual'è il campo chiave. Attenzione: non è detto che debba
essere per forza un campo identity, o numerico, o GUID come ho visto fare
nel progetto WilsonNHibernateExample. In realtà, il campo ID di NHibernate deve
essere una qualsiasi chiave primaria, perchè deve identificare in modo univoco
il record. Quando chiederemo ad NHibernate di caricare un'istanza salvata su
database, glielo chiederemo proprio dicendo: "Ue, caricami
l'istanza ID = 5", oppure "Ue, caricami
l'istanza ID = 'Igor Damiani'". Quindi, ripeto, deve essere il campo
primary key della tabella. Nel mio caso ho usato un campo identity (caso più
semplice), per cui questo si traduce in:
<id name='ID' column='ID' type='Int32' length='4' unsaved-value="0">
<generator class='identity' />
</id>
La proprietà name dice ad
NHibernate qual'è la proprietà del nostro business object. La proprietà
column dice come si chiama il campo
su database sul quale verrà salvata la proprietà. type è il tipo della proprietà, che nel nostro
caso è un Int32, ma potrebbe essere String, o qualsiasi altro
tipo di dato .NET. length è la
dimensione in bytes del campo. unsaved-value è un parametro
importantissimo, perchè indica ad NHibernate il valore della proprietà
quando una certa entità non è stata ancora salvata su database. In altre parole,
quando la proprietà ID del nostro businness object vale 0, NHibernate aggiungerà
un nuovo record alla tabella. Se ID è diverso da zero, NHibernate aggiornerà il
record già esistente. Il tag <generator> serve per espriemere una
politica di generazione del campo chiave: mettendo identity, diciamo ad
NHibernate che sarà SQL Server e restituirci il nuovo ID assegnato all'oggetto
(SELECT @@IDENTITY, ricordate?).
Detto questo, basta elencare le altre proprietà del nostro oggetto, indicando
per ciascuna di esse il nome della proprietà, il suo tipo, il campo in tabella e
la dimensione in bytes. Li riporto qui sotto:
<property name='Name' type='String' column='Name' length='50'/>
<property name='Height' type='Int32' column='Height' length='4'/>
<property name='Weight' type='Int32' column='Weight' length='4'/>
<property name='Number' type='Int32' column='Number' length='4'/>
Eventualmente, possiamo integrare per ogni tag <property> anche le proprietà not-null e unique.
Adesso arriva il bello, il motivo per il quale ho scritto questi due post.
Come facciamo a dire ad NHibernate che la classe HockeyPlayer espone una
proprietà Faults che è messa in una relazione uno-a-molti. Beh, una volta che si
sanno le cose, è tutto semplice. E' sufficiente usare il tag
<bag>, opportunamente gestito
per comunicare ad NHibernate quello che gli serve. Vediamolo nel dettaglio:
<bag name="Faults" cascade="all" lazy="false" inverse="true">
<key column="IDPlayer" />
<one-to-many class="Fault, HockeyObject" />
</bag>
Le proprietà di <bag> in
questo caso sono: name (nome della proprietà esposta da
HockeyPlayer), cascade (come ci si deve comportare nei
confronti di tabelle figlie), lazy ed inverse.
Al suo interno, dobbiamo assolutamente specificare il campo foreign-key nella
tabella per mantenere la relazione. Successivamente, c'è il tag
<one-to-many> che non fa altro che dire qual'è il tipo di
ogni singolo elemento esposto dalla proprietà Faults. Nel
nostro caso, il tipo è Fault. Questo è il grande segreto!
Adesso vediamo come mappare la classe Fault, ed il gioco è
fatto.
Il mapping per la classe Fault
Abbiamo detto che la
classe HockeyPlayer espone una proprietà Faults di tipo IList. Ogni istanza
contenuta in questa lista è di tipo Fault: se abbiamo la
necessità di persistere anche oggetti Fault, dobbiamo dire ad
NHibernate come farlo. Come ho già detto prima, non c'è bisogno di aggiungere un
file di mapping in più: è sufficiente accodare la seconda classe nello stesso
file. Per brevità, riporto tutto in un blocco solo il mapping che ho creato:
<class name='Fault' table='Faults'>
<id name='ID' column='ID' type='Int32' length='4' unsaved-value="0">
<generator class='identity' />
</id>
<property name='Reason' type='String' column='Reason' length='50'/>
<property name='When' type='DateTime' column='[When]' length='4'/>
<property name='FaultGravity' type='Int32' column='Gravity' length='4'/>
<property name='FaultConsequence' type='Int32' column='Consequence' length='4'/>
<many-to-one name="HockeyPlayer" column="IDPlayer"
class="HockeyPlayer, HockeyObject" cascade="none" />
</class>
Anche qui diciamo qual'è la tabella su cui salvare, diciamo tutto quello che
serve sul campo primary-key. Anche in questo caso, uso la proprietà
unsaved-value per dire qual'è il
valore che corrisponde ad una entità non ancora salvata. Poi, c'è l'elenco delle
proprietà, ciascuna delle quali fa riferimento ad un campo ben specifico su
database. Notare il campo When, che ho dovuto includere tra [ e ] per farlo
digerire a SQL Server.
Qui c'è una cosa particolare che voglio descrivere bene. La classe
Fault è legata alla classe HockeyPlayer
con una relazione molti-a-uno. Più istanze di Fault possono
essere relazionate ad una sola istanza di HockeyPlayer. Questo
concetto lo si trasmette ad NHibernate con il tag <many-to-one>, il quale
richiede il nome della classe padre (nel nostro caso HockeyPlayer), la proprietà
nell'oggetto figlio che punta all'oggetto padre ed il campo su database che
fa da foreign-key (IDPlayer).
I files di mapping sono pronti! Ed adesso?
Ok, dato per
assodato che i files di mapping sono pronti, possiamo passare finalmente a
scrivere codice C# e vedere realmente come si possono
leggere/scrivere/cancellare istanze dei nostri business object. Questo sarà tema
del mio terzo ed ultimo post su questo argomento.
Lascio anche un download, che potete liberamente
scaricare per dare un'occhiata a quello che ho fatto. E' una sample application
assolutamente priva di qualsiasi utilità , se non per il fatto di far vedere come usare
NHibernate nel contesto che ho descritto in questi due (finora) post. Nello zip
è incluso anche un file CreateDatabase.sql che serve per riprodurre la
struttura del database. Il codice è fornito as-is, non rispondo di
nulla. Chiedete pure su NHibernate (tanto io poi giro le domande a Janky ), ma di altro proprio no.