Nella mia piccola serie di post dedicati ad NHibernate
(1, 2, 3 e 4) abbiamo visto come creare un domain model e come
persisterlo con NHibernate. Abbiamo visto come mappare ogni singola proprietà,
come creare relazioni uno-a-molti, come implementare un data provider, etc. etc.
In questi ultimi giorni ho voluto a tutti i costi raggiungere un altro
risultato, per il quale ho davvero googlato molto, ho chiesto, ho letto. Molte
pagine sul Web riportano molte soluzioni diverse, che però non mi hanno
soddisfatto per nulla. Mi sto riferendo alla
possibilità di sfruttare collezioni fortemente tipizzate nel nostro domain
model, nell'ottica di avere un domain model che possa esporre
correttamente collection adatte a gestire gli oggetti che ci servono. E' passato
un po' di tempo da quei post, perciò preferisco riprendere la situazione per un
brevissimo riassunto.
Riassunto delle puntate precedenti
Nei post linkati
sopra, parlavamo semplicemente di una classe HockeyPlayer, che
esponevano una collection Faults. Il codice dei field privati,
copiati & incollati dal post originario, è questo:
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
}
Il succo del discorso è proprio questo. La proprietà
Faults è esposta attraverso l'interfaccia
IList, più che adatta per contenere uno o più elementi ma, lo
sappiamo bene, non tipizzati. Difatti, il costruttore della classe è questo:
public HockeyPlayer(string Name, int Height, int Weight, int Number)
{
name = Name;
height = Height;
weight = Weight;
number = Number;
faults = new ArrayList();
}
Il tipo concreto attraverso il quale utilizziamo la proprietà
Faults è un ArrayList che per sua natura può
contenere qualsiasi tipo di oggetti, e non è tipizzato. .NET 2.0 ci permette,
con i generics, di usare collection tipizzate, molto utili per rendere un po'
più robusta la nostra applicazione e il domain model sottostante. Modifichiamo
radicalmente quindi la classe HockeyPlayer e facciamo in modo che l'elenco dei
Faults di ogni giocatore sia type-safe. Innanzitutto, creiamo una classe
FaultsCollection che non fa altro che derivare dalla classe
BindingList<Fault>. Il codice è il seguente:
public class FaultsCollection : BindingList<Fault>
{
public FaultsCollection() { }
public FaultsCollection(IList lista)
{
foreach (Fault f in lista)
this.Add(f);
}
}
Ricordo che di default il compilatore crea automaticamente il costruttore
vuoto della classe, a patto però che non ne creiamo di nostri. Se creiamo un
secondo costruttore - come nel codice qui sopra - siamo obbligati ad esplicitare
anche il primo. Fatto questo, possiamo modificare la classe HockeyPlayer ed
utilizzare FaultsCollection.
// Field privato
private FaultsCollection faults;
// Proprietà pubblica
public FaultsCollection Faults
{
get { return faults; }
set { faults = value; NotifyPropertyChanged("Faults"); }
}
Nulla di particolarmente complicato. Il vero problema arriva adesso.
Persistiamo la classe modificata con NHibernate
La faccio
breve: se in un business object esponiamo una nostra custom collection (come
abbiamo fatto), NHibernate si lamenta un po'. Il motivo è che NHibernate tenta
di convertire la collection nella classe NHibernate.Collections.Bag. Tale classe
implementa l'interfaccia IList, allo stesso modo della nostra FaultsCollection.
Alla fine, dopo numerosi esperimenti e test, sono arrivato alla conclusione che
NHibernate riesce a castare FaultsCollection ad IList (e ci mancherebbe altro),
ma non riesce giustamente a castare da Bag e FaultsCollection.
Le soluzioni che ho trovate sul Web nelle settimane scorse parlano di creare
classi wrapper, oppure di usare i generics di NHibernate (e nella fattispecie la
classe EntitySet<T>), che tra l'altro fanno parte di una release ancora in
alpha). Le classi wrapper le ho escluse a priori: se ho n classi che espongono
custom collection, per ciascuna di esse devo creare la classe wrapper
corrispondente. Ad esempio, se ho HockeyPlayer, devo implementare anche
HockeyPlayerRepository, uguale in tutto e per tutto alla prima, solo che usa
IList invece della FaultsCollection. Ho voluto quindi creare un domain model
usando esclusivamente FX2.0, senza interferenze esterne.
La mia soluzione: una proprietà wrapper
Chiamatelo
workaround, chiamatelo sporco, chiamatelo come lo volete, ma io ho raggiunto un
ottimo compromesso creando semplicemente una seconda proprietà nascosta che non
fa altro che esporre FaultsCollection attraverso un casting a
IList.
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public IList NHFaults
{
get { return (IList)faults; }
set { faults = new FaultsCollection(value); }
}
L'accessor get non fa altro che castare, mentre il
set in realtà va a valorizzare faults
attraverso il costruttore che abbiamo visto prima, che accetta un solo parametro
in input di tipo IList. Attraverso l'utilizzo degli attributi
Browsable e EditorBrowsable, evito che questa
proprietà venga visualizzata nell'Intellisense o in un'eventuale finestra
Proprietà. Fatto questo, ho dovuto poi modificare il file di mapping di
NHibernate: il motore di persistenza dovrà usare la proprietà NHFaults, che in
realtà accede ai dati contenuti nel field privato faults.
Qualche dettaglio in più...
Ci tengo a precisare che noi
nel nostro codice non dovremmo mai usare NHFaults. Questo è il motivo per cui
l'ho resa nascosta con gli attributi di cui parlavamo prima. Il domain model
deve sempre e comunque passare dalla collection tipizzata
FaultsCollection, creata proprio per questo motivo. L'unico componente che
utilizzerà NHFaults sarà proprio NHibernate che:
- quando chiede all'oggetto il valore di NHFaults, lo ottiene semplicemente
castando a IList la nostra custom collection
- quando imposta il valore, valorizzerà direttamente
FaultsCollection
Ogni osservazione o critica, se costruttiva ed intelligente, è
ovviamente ben accetta!