Sono impazzito molto nei giorni scorsi nel tentativo di sfruttare NHibernate per persistere
business object del domain model di un'applicazione reale che sto sviluppando. Finchè
si tratta di oggetti semplici, nessun problema, ma nel mio caso avevo a che fare
con due oggetti relazionati uno-a-molti. Ieri sera ho risolto il mio problema,
ma ho voluto replicare lo stesso scenario in una sample
application
di cui ho già parlato in passato, per descrivere meglio il mio
dilemma e per spiegare come l'ho risolto.
Le classi coinvolte: HockeyPlayer e Fault
La classe HockeyPlayer rappresenta un giocatore di hockey, con tutte le proprietà che possono
interessare. La proprietà ID (di tipo int) è la mia
chiave. Poi ho Name, Height, Weight, Number e Faults. Quest'ultima
rappresenta l'elenco dei falli che il giocatore ha subìto durante una
partita. Inizialmente, la proprietà Faults era di tipo FaultsCollection, che a
sua volta ereditava direttamente da BindingList<Fault>. Tutto abbastanza semplice,
tutto sommato. Qual'era il mio dilemma?
Volevo usare NHibernate per salvare su database ogni istanza di HockeyPlayer:
d'altronde NHibernate serve proprio per questo. Una volta definite le classi, è
sufficiente creare il file di mapping corretto, e scrivere qualche linea di
codice C# per ottenere quello che vogliamo. Fosse facile! A parte scherzi, come potete
immaginare la questione è tutta qui, ma non solo. Voglio annotare alcuni punti
importanti che a me erano sfuggiti, magari a chi è un po' più veterano di
NHibernate possono sembrare banalità.
- NHibernate non può persistere oggetti di tipo
BindingList. Per questo motivo, nella mia sample
application ho eliminato la classe FaultsCollection. La proprietà Faults di
HockeyPlayer è di tipo IList, che al contrario uno di quei
tipi con cui NHibernate lavora nativamente.
- Ogni istanza della classe Fault deve avere un riferimento al suo
HockeyPlayer, in modo che NHibernate possa gestire correttamente la
foreign-key sulla tabella.
Detto questo, andiamo avanti. La classe HockeyPlayer si
presenta essenzialmente così:
public class HockeyPlayer : INotifyPropertyChanged
{
#region Field
private int id;
private string name;
private int height;
private int weight;
private int number;
private IList faults;
#endregion
}
Per brevità, ho riportato solamente i field privati. Le proprietà
pubbliche non fanno nulla di particolare: hanno solamente la chiamata a
NotifyPropertyChanged per il databinding. Notare che, come
dicevo prima, l'elenco dei falli di ciascun giocatore è esposto tramite
IList. I field privati della classe Fault sono i
seguenti:
public class Fault
{
#region Field
private int _id;
private string _reason;
private DateTime _when;
private FaultGravityEnum _gravity;
private FaultConsequenceEnum _consequence;
private HockeyPlayer _hockeyPlayer;
#endregion
}
Anche qui, ad ogni field corrisponde evidentemente una proprietà
pubblica. Notare la presenza della proprietà HockeyPlayer, che è un riferimento
allo HockeyPlayer a cui è associato il Fault. FaultGravityEnum
e FaultConsequenceEnum sono due semplici enum che ho definito
per comodità.
Un tipico uso di questo domain model
Detto questo, io
posso scrivere codice C# per istanziare questi oggetti. Ad esempio:
Fault ft = new Fault("Push & Pop! :-)", DateTime.Now);
ft.FaultConsequence = FaultConsequenceEnum.Penalty;
ft.FaultGravity = FaultGravityEnum.Low;
HockeyPlayer player = new HockeyPlayer("Mike Modano", 183, 100, 28);
ft.HockeyPlayer = player;
player.Faults.Add(ft);
Qui creo un oggetto Fault, con dei valori a caso.
Successivamente creo uno HockeyPlayer ed associo il Fault creato prima,
ricordandomi di passare il riferimento all'oggetto padre. Come posso persistere
questo oggetto? Come posso utilizzare NHibernate per dire: "
Salvami l'oggetto player!". Trattandosi di due
business object legati da una relazione uno-a-molti, così sarà evidentemente
anche su database: avrò una tabella HockeyPlayers per mantenere
tutte le istanza dei giocatori, ed una tabella Faults che
conterrà i Faults di ciascun giocatore. In questa tabella, avrò un campo
IDHockeyPlayer che sarà la foreign-key per mantenere il legame.
Dopo questa prima richiesta (salvataggio dell'oggetto),
ne seguono altre due: quando recupero l'oggetto HockeyPlayer
salvato, mi verranno recuperati anche i Fault ad esso associati? Se cancello
l'oggetto, mi cancella anche i Faults ad esso legati?
Quest'ultima, in particolare, è obbligatoria, altrimenti il
database mi bloccherebbe l'operazione, perchè avrei una violazione di
chiave.
Alla prossima...
Adesso non ho tempo, il lavoro chiama, ma
ho tutto il materiale qui pronto per essere pubblicato. Spero di riuscire a farlo
quanto prima. Forse sto buttando via del tempo, ma credo che sia utile, perchè
troppe cose vengono date per scontate. Ho perso tempo io, ma se posso evitarlo a
voi, tanto meglio!