Domain Model, NHibernate (con lazy load = true) e WPF
Prevedo la stesura di un post piuttosto lungo. Cominciamo dagli ingredienti:
- Un’entità sul nostro domain model (Artista) definita in un assembly (DomainModel)
- Un assembly (DataAccessLayer) con un po’ di classi che, tramite NHibernate, si occupano di persistere le entità definite nell’assembly al punto (1) su database (SQL Server)
- Un altro assembly contenente un progetto WPF – dettagli più sotto
Tutto questo per garantire la separazione delle componenti software di un progetto: domain-model, dal e UI. Almeno queste. Non voglio fare il saccente, ma credo che la difficoltà sia Alta, ed i tempi di preparazione lunghi. E penso anche che darò un sacco di cose per scontate, e ciò non va bene. Pazienza. Mi piacerebbe estrapolare in un piccolo progetto la problematica che ho incontrato: e lo farò.
Introduzione
Supponiamo di avere una classe Artista che espone un certo numero di proprietà pubbliche. Tale classe fa parte del nostro domain-model. Tale classe è anche mappata, attraverso NHibernate, su un database SQL Server. Immaginatevi quindi proprietà come Cognome, Nome, DataNascita, Sesso, Disponibilita, tutte mappate su una tabella Anagrafica con i campi con lo stesso nome della proprietà, per semplicità.
Lo scopo è quello di far vivere un’istanza di Artista su una Window di WPF. Con “vivere” intendo l’intero ciclo di vita di un’istanza: creazione di un nuovo artista, inserimento valori, salvataggio. Oppure rilettura di una precedente istanza, modifica dei valori e salvataggio. Ignoriamo tutte le proprietà della classe Artista e teniamo solo Cognome.
Cominciamo.
La Window di WPF
Lo XAML è il seguente:
<Window x:Class="VivendoByte.CastingManager.TestWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Casting Manager" Height="300" Width="400" Background="LightCyan"> <TextBox x:Name="CognomeTextBox" Height="20" Margin="8" Text="{Binding Path=Cognome}" /> </Window>
L’unico elemento nella Window è una TextBox, che è bindata alla proprietà Cognome di un’ipotetica istanza di Artista. Diamo un’occhiata al code-behind di questa classe:
public partial class TestWindow : System.Windows.Window { Artista _artista; public TestWindow() { InitializeComponent(); _artista = new Artista("Liborio", "Damiani"); this.DataContext = _artista; } }
La Window ha un field privato _artista, che vive all’interno della Window stessa. Nel costruttore creo l’istanza, dando un nome ed un cognome fittizi. Poi dico che il DataContext della Window è l’istanza appena creata. Quando la Window appare su schermo, il risultato è il seguente:
La TextBox mostra il cognome. Adesso arriva il bello: passiamo ad usare NHibernate.
Utilizzare NHibernate con WPF
Senza scendere troppo nel dettaglio, usiamo NHibernate per caricare da db un’istanza precedentemente persistita. Nel costruttore, invece di fare new Artista(), diciamo ad un ipotetico data provider (ipotetico per voi, ma non per me) di caricare un’istanza: ad esempio, quella con ID = 11. Vediamo come modifichiamo il code-behind:
1 public partial class TestWindow : System.Windows.Window 2 { 3 Artista _artista; 4 DataProvider<Artista> _provider; 5 6 public TestWindow() 7 { 8 InitializeComponent(); 9 _provider = new DataProvider<Artista>(); 10 _artista = _provider.LoadObject(11); 11 this.DataContext = _artista; 12 } 13 }
Abbiamo un data provider generico, che viene istanziato nel costruttore. L’istanza di Artista viene caricata da database, prelevando quella con ID = 11. Infine viene impostato il DataContext della Window.
Qui abbiamo un grave problema. Se NHibernate è impostato per caricare le istanze con lazy=true, otteniamo un’exception, mentre se decidiamo di non utilizzare il lazy load tutto andrà a buon fine. Il tipo di oggetto ritornato dalla LoadObject(11) qui sopra dipende a seconda di come impostiamo il lazy:
Se Lazy Load = true, _artista.GetType() è CProxyTypeVivendoByte_CastingManager_DomainModelArtistaDomainModel_NHibernate_ProxyINHibernateProxy1
Se Lazy Load = false, _artista.GetType() è VivendoByte.CastingManager.DomainModel.Artista
Se utilizziamo il lazy load, quindi, NHibernate ci ritorna un tipo di oggetto che non è quello che ci aspettiamo per il data-binding. Ricordo in breve che il lazy load di NHibernate permette il caricamento ritardato di un’istanza di un oggetto – caricamento che avviene realmente solo nel momento in cui abbiamo effettivamente bisogno di utilizzarlo. Per esempio, se facciamo eseguire dal debugger la linea (10), vedremo come le proprietà non siano valorizzate come ci si aspetta, ma esse continuano a valore null; stessa cosa per i membri privati. In sostanza, il tipo ritornato con lazy=true non è nient’altro che una sorta di wrapper attorno all’istanza di Artista, un wrapper che si occupa di andare a leggere l’istanza tramite la session di NHibernate che deve rimanere aperta.
Il tipo di caricamento va impostato nel file di mapping di NHibernate, per ciascuna classe mappata:
<class name="Artista" table="Anagrafica" lazy="false"> ... ... </class>
Comunque, nel momento in cui tentiamo di impostare il DataContext della Window con un tipo non corretto, otteniamo una System.Reflection.AmbiguousMatchException. Un errore che sinceramente non sono riuscito a decifrare davvero fino in fondo. Ho litigato anche qui per un sacco di tempo, convintissimo che il problema fosse un mio non corretto utilizzo del DataContext di WPF, ma poi alla fin fine ho dovuto concentrarmi sul comportamento di NHibernate. E l’inghippo è saltato fuori, anche grazie all’aiuto di un amico.
Conclusione
Sono arrivato fin troppo velocemente alla fine. Credo ci siano ancora un milione di cose da dire. Note varie: la classe Artista di questo esempio implementa l’interfaccia INotifyPropertyChanged, per poter gestire correttamente il meccanismo di data-binding. Non appena reimposto il DataContext della Window, quindi, tutti i controlli riflettono il cambiamento, sia che l’istanza di Artista venga caricata da database oppure che venga creata direttamente una nuova istanza.
Technorati Tags: .NET programming WPF NHibernate DAL DomainModel