Da quando ho scoperto NHibernate, ormai più di un anno fa, ho sempre disegnato il mio domain-model in modo tale che far persistere le mie entità con questo framework fosse il più naturale possibile. Dal punto di vista pratico, ciò significa che tutte le entità del domain-model derivino tutte da una classe astratta Entity, che definisce una sola proprietà ID di tipo int.
public abstract class Entity
{
public int ID
{
get { return _id; }
set { _id = value; }
}
}
Ogni volta che una classe del mio domain-model deve essere persistita attraverso NHibernate, la faccio derivare da Entity. In questo modo, ogni classe espone una proprietà ID che contiene la chiave univoca dell'istanza. Ovviamente tutte le entità hanno un unsaved-value = 0, e questo permette all'engine di NHibernate di capire in che modo persistere una determinata istanza sul database. Quando ID = 0, l'oggetto esiste solo in memoria (oggetto transiente) e non è stato ancora persistito. Se ID != 0, l'oggetto in memoria esiste anche sul database (oggetto persistente).
Tutto deriva da Entity? Bene, un vantaggio anche per il mio DAL
Il fatto che tutte le mie entità derivino dalla classe astratta Entity porta anche un vantaggio all'interno del DAL. Di solito, i miei DAL utilizzano i generics. Pertanto, posso applicare un constraint, dicendo che il tipo T deve per forza rappresentare una classe derivata da Entity. La morale di tutto questo è che non posso istanziare un'istanza di DataProvider<int> o un DataProvider<MyAppSettings>, ma solo di DataProvider<Artista>, DataProvider<Auto>, DataProvider<Competenza> e così via, cioè solo di quelle classi che so che dovranno essere rese persistenti da NHibernate.
Fin qua nulla di male. Cosa succede però se devo creare un domain-model da utilizzare all'interno di un'applicazione WPF? Con .NET 2.0, ad esempio, per poter sfruttare le potenzialità di data-binding sulle Windows Forms era opportuno creare un domain-model le cui classi implementassero l'interfaccia INotifyPropertyChanged. Questa interfaccia consente l'implementazione di un data-binding efficiente tra un oggetto ed i controlli sulla UI, e viceversa. Questo approccio funziona ancora in WPF, non è cambiato nulla da questo punto di vista, ma per tutta una serie di ragioni è opportuno ragionare in modo diverso.
Perchè utilizzare le dependency properties?
Questo "modo diverso" consiste nell'utilizzare le dependency properties, che consentono di sfruttare features avanzate come data-binding serio, che con WPF va molto al di là delle potenzialità offerte da .NET 2.0. Per esempio, potremmo bindare la proprietà Eta della classe Artista con uno Shape di qualche tipo, in modo tale che a seconda dell'età il Rectangle che abbiamo sulla Window si allunghi o si accorci. Oppure, potremmo definire un trigger sulla proprietà booleana PossiedeAuto, in modo tale che quando vale true, appaia un'immagine da qualche parte, altrimenti no. In generale, molte delle funzionalità di WPF (stili, template, trigger, data-binding, animazioni, etc.) sfruttano proprio le dependency properties: se sappiamo che il nostro domain-model dovrà in qualche modo interagire con l'engine di WPF, forse è bene pensarci prima e disegnare le nostri classi in modo opportuno.
Quando un oggetto intende utilizzare le dependency properties, deve derivare dalla classe DependencyObject di WPF. Qui abbiamo un problema: le entità del nostro domain-model dovrebbero derivare da due classi, sia DependencyObject che Entity. Questo in .NET non è possibile, in quanto non è prevista ereditarietà multipla. Poco male, è sufficiente pensare diversamente e invece di aver a che fare con una classe Entity, definiamo un'interfaccia IPersistableEntity (dal nome altisonante :-).
using System;
using System.Windows;
namespace DomainModel
{
public interface IPersistableEntity
{
int ID { get; set; }
}
public class Artista : DependencyObject, IPersistableEntity
{
public static readonly DependencyProperty IDProperty;
public int ID
{
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}
static Artista()
{
Type tp = typeof(Artista);
IDProperty = DependencyProperty.Register("ID", typeof(int), tp);
}
}
}
La classe Artista qui sopra è stata compressa/adattata/sistemata per riportare parte del codice. Il codice di produzione è evidentemente organizzato un po' meglio e migliorato sotto diversi aspetti. Comunque sia, la classe eredita da DependencyObject ed implementa l'interfaccia IPersistableEntity. Quest'ultima in particolare richiede l'implementazione di una proprietà ID di tipo int, che è stata realizzata come dependency property. Questo implica una serie di cose che sono ben visibili dal codice: un field pubblico IDProperty di tipo DependencyProperty, un costruttore privato che registra tutte le dependency properties che mi servono.
Conclusione
Implementando il domain-model secondo questi criteri, sviluppo classi che possono lavorare sull'interfaccia di WPF e possono essere persistite da NHibernate passando dal DAL del progetto che sto sviluppando (progetto per una volta non freeware).