Gestione della persistenza con NHibernate

3 pagine in totale: <<Indietro 1 2 [3]

La lettura di una entità dalla base dati a partire dal suo identificativo è altrettanto semplice ed avviene utilizzando il metodo Session.Get.

// apro la session e recupero la fattura dato il suo id
using (ISession session = SessionHelper.GetSession())
{
  fattura = session.Get<Fattura>(idFattura);

  // a questo punto, si vede dal profiler, sono stati letti gli oggetti
  // fattura e cliente. Fattura.Dettagli è un proxy che eseguirà la
  // query al primo accesso ad un membro della collection.

  // questa assegnazione scatena, in modo trasparente, una query di
  // SELECT del dettaglio (verificare con profiler)
  int count = fattura.Dettagli.Count;
}

Qui è particolarmente importante analizzare il comportamento di NHibernate, perché è solitamente in questa fase che si gioca la bontà delle prestazioni di un'applicazione. In base al mapping che è stato precedentemente realizzato, il recupero di una fattura comporta l'esecuzione immediata di una sola query, in cui le tabelle FATTURE e CLIENTE sono in join; lo possiamo facilmente verificare con un tool come SQL Profiler. La query generata è del tipo:

SELECT fattura0_.Id as Id2_1_, fattura0_.Numero as Numero2_1_, ....
FROM FATTURE fattura0_ left outer join CLIENTI cliente1_ on ....
WHERE fattura0_.Id=@p0

I dettagli, invece, proprio in virtù dell'impostazione di lazy load, vengono popolati al volo solo quando ne abbiamo effettivamente bisogno (nel nostro esempio, nel momento in cui viene valorizzata la variabile count).

Non è questa la sede più adatta per dilungarsi sulle strategie di fetching messe a disposizione da NHibernate: ognuna ha i suoi pro e contro e la scelta corretta non può prescindere dal particolare contesto applicativo. NHibernate consente comunque di personalizzare completamente questi comportamenti, sia staticamente sul mapping che a runtime, permettendo quindi di effettuare un vero e proprio tuning dell'applicazione. Si tratta di una peculiarità decisamente importante, la cui implementazione in uno strato di persistenza realizzato ad-hoc non è affatto banale. Ad esempio, basta modificare il file Fattura.hbm.xml, inserendo l'attributo Fetch="Select" nell'elemento many-to-one, per far sì che la fattura e il relativo cliente siano recuperati in due distinte SELECT.

SELECT fattura0_.Id as Id2_0_, fattura0_.Numero as Numero2_0_, ...
FROM FATTURE fattura0_
WHERE fattura0_.Id=@p0

SELECT cliente0_.Id as Id0_0_, cliente0_.RagioneSociale as ...
FROM CLIENTI cliente0_
WHERE cliente0_.Id=@p0

Questa scelta può rivelarsi valida, ad esempio, per evitare che molteplici relazioni many-to-one si traducano in un gran numero di join che potrebbero penalizzare le prestazioni, soprattutto alla luce del fatto che, grazie alla funzionalità di cache di primo livello, una Session ha la capacità di accorgersi che un certo cliente è magari già in memoria e pertanto non è necessario tornare ad interrogare il database per recuperarne le informazioni.

Cenni conclusivi

NHibernate in realtà offre molto di più di ciò che si è potuto mostrare in un articolo introduttivo come questo. Le citate funzionalità di fetch, ad esempio, comprendono anche un modello a oggetti per poter rappresentare interrogazioni complesse verso la base dati (Query by Criteria) e un linguaggio denominato HQL (Hibernate Query Language), del tutto simile a SQL, ma orientato agli oggetti, che consente di scrivere query simili a:

from Fattura f where f.Cliente.Sede.Indirizzo.Citta.Provincia.Sigla="MI"

Recuperare tutte le fatture dei clienti con sede a Milano, come nell'esempio, diviene decisamente più espressivo della corrispondente query SQL. È compito del framework di persistenza tradurre l'istruzione precedente in una query specifica per il DBMS in uso. Tutto ciò ha, tra l'altro, una conseguenza importante: il codice applicativo non ha alcun riferimento esplicito al particolare DBMS utilizzato e l'unica indicazione specifica si trova nel file di configurazione.

Del resto nel web.config (o nell'app.config) è possibile configurare il driver e il particolare dialetto SQL da utilizzare, con il risultato che le applicazioni che fanno uso di NHibernate diventano intrinsecamente cross-database. Sono infatti supportati i più famosi prodotti in commercio (SQL Server, Oracle, MySql, Access, PostgreSql, tanto per citarne alcuni) e, nella maggior parte dei casi, è sufficiente agire sulle impostazioni citate affinché la nostra applicazione funzioni correttamente in ogni scenario, senza necessità di modificare una singola riga di codice!

<nhibernate>
  ....
  <add key="hibernate.connection.driver_class"
       value="NHibernate.Driver.SqlClientDriver" />
  <add key="hibernate.dialect"
       value="NHibernate.Dialect.MsSql2000Dialect" />
  ....
</nhibernate>

Concludendo, realizzare un'applicazione che utilizza un domain model è un'operazione complessa nel momento in cui si prendono in considerazione le problematiche legate alla persistenza su una base dati relazionale. Avvalersi di NHibernate significa:

  • concentrare gli sforzi implementativi ad un livello di astrazione più elevato del tedioso e ripetitivo codice SQL, con un notevole impulso alla produttività e alla manutenibilità;
  • avere minori rischi di introdurre comportamenti anomali o errati, anche solo per errori di battitura;
  • non legarsi ad un motore di database specifico.

Si tratta indubbiamente di vantaggi innegabili, amplificati enormemente dalla presenza di una serie di funzionalità avanzate (due livelli di cache, gestione della concorrenza, fine-tuning delle strategie di recupero dati, transparent write behind) le quali, per ovvi motivi, non possono essere trattate in un articolo introduttivo, ma che rivestono una grande importanza nel caso di applicazioni di elevata complessità e che, purtroppo, non sono affatto banali da realizzare in un layer di persistenza proprietario.

Il prezzo (basso) da pagare è la scrittura di file di mapping in XML per descrivere le entità del dominio applicativo. A questo proposito è doverosa una piccola nota; il dialetto XML utilizzato da NHibernate è estremamente espressivo e offre pieno supporto tanto ad object model complessi che facciano uso di associazioni, composizioni, ereditarietà e polimorfismo, quanto a schema di dati legacy, in cui, ad esempio, le tabelle siano dotate di chiavi composte. In situazioni simili questi file possono diventare anche piuttosto complessi e può risultare molto utile avvalersi di tool che ne permettano la compilazione visuale, tra cui NHibernate Domain Mapper, ad opera di Giancarlo Sudano e di chi scrive, attualmente ancora in lavorazione, ma liberamente scaricabile da Codeplex.

3 pagine in totale: <<Indietro 1 2 [3]

Attenzione: Questo articolo contiene un allegato

Contenuti dell'articolo

Commenti
Dai un voto a questo articolo, ci aiuterà a migliorare il nostro sito (1 è il voto minimo, 5 il massimo).

Per procedere al rating dell'articolo devi essere autenticato.

TUTORIALS
TOP TEN ARTICOLI
NOTIFICHE

Iscriviti alla nostra newsletter nuoviarticoli per ricevere e-mail le notifiche!

Indirizzo e-mail:
PROVIDER ASP.NET 2.0

Seleziona il database per avere il web.config pronto per Membership, Roles e Profile API.



IN EVIDENZA
MISC