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

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

Introduzione
Eccoci al terzo (ultimo?) post dedicato ad NHibernate. Nel primo abbiamo fatto un'overview delle classi da persistere, alcune delle quali legate da una relazione uno-a-molti, nel secondo abbiamo visto come creare files di mapping corretti che possano fornire all'engine di persistenza di NHibernate tutte le informazioni su ciascuna classe e come le classe si relazionano fra loro.

Fino ad oggi, non abbiamo scritto una sola maledetta linea di codice. In realtà, almeno per quello che riguarda me, scrivere codice C# che utilizzi NHibernate per caricare/scrivere/cancellare i nostri oggetti è davvero una banalità, perchè il fulcro, tutta la logica e molta della complessità è inserita nei files di mapping. Fatti quelli, la stragrande parte del lavoro è fatta. Nel post di oggi vediamo (finalmente) come scrivere un semplice dataprovider che esponga un'interfaccia (3 metodi) per fare tutto quello che ci serve.

La classe DataProvider<T>
La prima cosa che viene in mente è: "Ok, faccio una classe con un metodo LoadHockeyPlayer che accetta un ID e ritorna un'istanza di HockeyPlayer". Ricordiamoci innanzitutto di una cosa: l'ID di cui parlo è lo stesso ID che abbiamo specificato nel file di mapping. Rivediamolo un attimo:

<id name='ID' column='ID' type='Int32' length='4' unsaved-value="0">
    <generator 
class='identity' />
<
/id>

In questo caso, l'ID è il campo identity della tabella, ma avremmo potuto utilizzare anche il campo Name, ad esempio. Per una mia scelta, utilizzeremo un campo numero: l'identity. Quindi, potremmo pensare di implementare un metodo LoadPlayer più o meno così:

public HockeyPlayer LoadPlayer(int ID)
{
    HockeyPlayer ret;   
    
// Dico ad NHibernate di caricare l'oggetto con ID = ID

    
return(ret);
}

Il metodo accetta un qualsiasi int, e ritorna tramite NHibernate l'istanza corrispondente a questo ID. Se le classi coinvolte nel nostro domain model fossero 50, dovremmo scrivere 50 metodi diversi, ognuno dei quali con un nome diverso (LoadFault, LoadSquad, LoadStadium, tanto per citarne qualcuna a caso). La struttura del metodo sarà sempre la stessa: cambierà solo il tipo ritornato. Duplicazione di codice a tutto andare, senza pensare poi ai metodi per il salvataggio e la cancellazione. Insomma, tutto fuorchè elegante, comodo e manutenibile.

In realtà con l'avvento di .NET 2.0 possiamo risolvere brillantemente la questione con i generics. Tutti gli oggetti del mio domain model ereditano da una classe astratta Entity. Tutte, comprese quindi l'oggetto HockeyPlayer, Fault e così via. Vi state chiedendo cosa c'entra questo? E' presto detto. Così facendo possiamo creare una sola classe DataProvider generica che espone i 3 metodi Load, Save e Delete e che verrà istanziata di volta in volta a seconda delle necessità. Vediamo il codice.

public class DataProvider<T> where T : AbstractEntity
{
    
public static void Save(T oggetto) { }
    
public static T Load(int ID) { }
    
public static void Delete(T oggetto) { }
}

I tre metodi sono statici. Impongo che il tipo T derivi da AbstractEntity, in modo tale che forziamo il data provider a lavorare solo con i tipi appartenenti al nostro domain model. Il resto credo che sia piuttosto chiaro. Il metodo Save accetta come parametro l'oggetto da salvare, di tipo T. Il metodo Load lo restituisce, dato in input l'ID da caricare. Il metodo Delete infine cancella un oggetto.

Come si utilizza? Nulla di particolare: sulla Windows Forms della mia sample application ho inserito un Button per il caricamento, che non fa nient'altro che:

private void btnLoad_Click(object sender, EventArgs e)
{
    
int id = 6;

    player = DataProvider<HockeyPlayer>.Load(id);
    SetDataBindings();
}

A scopo di test, carico sempre l'istanza con ID = 6. Brutto da vedere, ne sono cosciente.  Stessa cosa per gli altri due metodi:

private void btnDelete_Click(object sender, EventArgs e)
{
    DataProvider<HockeyPlayer>.Delete(player);
}

private void btnSave_Click(object sender, EventArgs e)
{
    DataProvider<HockeyPlayer>.Save(player);
}

Ditemi che è complicato, e vi ammazzo!

Sì, va bene, ma come diavolo fai?
Se vi state chiedendo questo, continuate a leggere. Fino ad adesso, non abbiamo visto ancora nulla di NHibernate, ho solo descritto a grandi linee come funziona il mio data provider. Cosa implementa ciascuno dei metodi coinvolti, cioè il Load, il Save ed il Delete?

La prima cosa da sapere è che NHibernate ha bisogno di una configurazione, in buona parte letta dell'app.config associato all'eseguibile. Considerato che trovate un miliardo di siti che vi fanno vedere questa cosa, preferisco parlare d'altro. La mia classe DataProvider contiene tre field privati che mantengono tale configurazione, più una variabile bool che indica se il data provider è già stato inizializzato oppure no. Tali field sono i seguenti:

static bool init = false;
static Configuration nhibernateConfiguration;
static ISessionFactory factory;
static ISession session;

All'ingresso di ciascun dei due metodi, controllo init: se vale false, significa che è la prima volta che utilizzo il data provider, e quindi ne devo settare la configurazione:

if (!init) initialize();

Il metodo initialize è un altro metodo statico privato che inizializza la classe DataProvider:

#region Initialize
private static void 
initialize()
{
    
/* Inizializza, se necessario, l'engine
     * di NHibernate */
    
nhibernateConfiguration = new Configuration();
    
/* Qui devo aggiungere l'assembly che contiene i
     * files di mapping di NHibernate */
    
nhibernateConfiguration.AddAssembly("DataBindingSample");
    nhibernateConfiguration.AddAssembly("HockeyObject");
    factory = nhibernateConfiguration.BuildSessionFactory();
    init = 
true;
}
#endregion

La parte interessante del metodo initialize a parer mio è quella riguardante l'aggiunta dell'assembly (o degli assembly) che contengono i files di mapping. In altre parole, sull'oggetto nhibernateConfiguration chiamo il metodo AddAssembly per poter dare all'engine la possibilità di caricarsi tutti i files di mappings che abbiamo definito. Nota importante: quando comincerete a lavorare con NHibernate, probabilmente incontrerete sulla vostra strada parecchi errori, di svariati tipi. Se non vi succederà, significa che tutta la community di NHibernate sparsa per il Web (compreso il mio piccolo contributo in questo frangente) ha spiegato bene le cose: ve lo auguro di cuore. Nel caso contrario, sappiate una cosa: alcuni tipi di errori li troverete proprio sulla chiamata a AddAssembly, quando il files di mapping ha qualcosa che non va a livello sintattico, oppure quando avete scritto nomi di proprietà non valide che non appartengono allo schema di NHiernate. Questi errori sono i più gravi, perchè significa che il file di mapping non è valido e di conseguenza non viene neppure caricato. Altri errori invece dipendono dagli oggetti, e quindi appariranno solo quando necessario: se salvo un oggetto la cui proprietà Denominazione è vuota, ma sul database il campo è marcato come not-null, allora NHibernate ci avviserà solo durante il salvataggio stesso.

Una volta che la classe Data Provider è stata inizializzata correttamente, possiamo vedere l'implementazione di ciascuno dei 3 metodi di cui finora abbiamo solo parlato. Partiamo dal metodo Load:

public static T Load(int ID)
{
    
if (!init) initialize();
    session = factory.OpenSession();
    T ret =(T) session.Load(
typeof(T), ID);
    session.Close();
    
return(ret);
}

Se init è true, apro una sessione, istanzio un oggetto di tipo T, lo carico, chiudo la sessione e ritorno l'oggetto. Lineare, semplice ed efficace. Cosa volere di più? Passiamo al metodo Save:

public static void Save(T oggetto)
{
    
if (!init) initialize();
    session = factory.OpenSession();
    ITransaction transaction = session.BeginTransaction();
    
// Comunichiamo ad NHibernate che questo oggetto deve essere salvato
    
session.SaveOrUpdate(oggetto);
    transaction.Commit();
    
// Commit dei cambiamenti al database e chiusura della ISession
    
session.Close();
}

Infine, il metodo Delete che chiude le danze:

public static void Delete(T oggetto)
{
    
if (!init) initialize();
    session = factory.OpenSession();
    
// Comunichiamo ad NHibernate che questo oggetto deve essere salvato
    
session.Delete(oggetto);
    
// Commit dei cambiamenti al database e chiusura della ISession
    
session.Close();
}

Download della sample application
Il codice aggiornato di questa piccola applicazione è scaricabile qui!

powered by IMHO 1.2

Print | posted on Friday, June 16, 2006 4:49 PM | Filed Under [ Sviluppo .NET ]

Feedback

Gravatar

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

6/23/2006 3:37 PM | Technology Experience
Gravatar

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

7/19/2006 12:51 PM | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET