Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[2] NHibernate: un caso tratto da un'applicazione reale

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.

powered by IMHO 1.2

Print | posted on Wednesday, June 14, 2006 2:27 PM | Filed Under [ Sviluppo .NET ]

Feedback

Gravatar

# re: [2] NHibernate: un caso tratto da un'applicazione reale

Ti prego...
cambia il cascade da "all" a "all-delete-orphan"...sulla classe parent

Altra cosa:
Anche se in SQL Server la cosa non cambia...
per il generator,
conviene sempre usare un bel "native",
così ti levi il cruccio di sapere come lavora SQL in maniera nativa, piuttosto che DB2...fa tutto lui...
Per il resto...bravo!
6/14/2006 2:47 PM | Giancarlo Sudano
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET