Crad's .NET Blog

L'UGIblog di Marco De Sanctis
posts - 190, comments - 457, trackbacks - 70

[NHibernate] LazyLoad delle entities

Il pattern LazyLoad è quasi sempre utilizzato in NHibernate per il caricamento delle collection di childs in una relazione Master/Detail. Non tutti sanno invece che è possibile applicare questo pattern a tutti i tipi di relazioni, potendo così ottimizzare di molto la dimensione delle query che vengono effettuate su DB e la mole di dati che viene recuperata.

Capita spesso di avere classi con parecchie relazioni Many-To-One, vuoi perché si tratta di classi "dettaglio" di qualcosa, vuoi perché se creo la classe Articolo magari questa espone una proprietà Tipo di tipo (scusate il gioco di parole) TipoArticolo, ecc.ecc... Se non si utilizza il LazyLoad anche per questo tipo di relazioni, NHibernate effettua query con un gran numero di Join che, come sappiamo, possono minare le performance delle applicazioni.

Ho scritto una piccola applicazione di esempio, scaricabile a questo link , che persiste sul DB un Articolo e un TipoArticolo e poi ne forza una lettura. Analizziamo il mapping di Articolo:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0" default-lazy="false" 
    
default-access="field.camelcase" >
    <
class name="NHLazyTest.Articolo, NHLazyTest" table="Articoli">
        
        
<!-- .... un po' di cose qui .... -->
        
        
<many-to-one name="Tipo" class="NHLazyTest.TipoArticolo, NHLazyTest" />
    <
/class>
<
/hibernate-mapping>

Come si vede, c'è un riferimento many-to-one alla classe TipoArticolo, che a sua volta ha il seguente mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0" 
    
default-lazy="false" default-access="field.camelcase" >
    <
class name="NHLazyTest.TipoArticolo, NHLazyTest" table="TipiArticolo" >
        <!-- .... un po' di cose qui .... -->
    </class>
<
/hibernate-mapping>

Non è che ci sia molto da commentare, se non quel default-lazy=false per modificare il comportamento standard di NHibernate 1.2 che tende a caricare tutte le entity in LazyLoad e che, nel caso si utilizzi una versione precedente, è perfettamente inutile!

Se eseguiamo una lettura di un articolo dal DataBase, tramite il seguente snippet di codice

using (ISession session = SessionHelper.GetSession())
{
    articolo = session.Get<Articolo>(id);
}

il risultato sarà che NHibernate proverà a recuperare tutti i dati da DB con una sola query, che sarà simile alla seguente:

exec sp_executesql N'SELECT articolo0_.ID_Articolo as ID1_1_, articolo0_.Tipo as Tipo1_1_, 
articolo0_.Descrizione as Descrizi2_1_1_, tipoartico1_.ID_TipoArticolo as ID1_0_, 
tipoartico1_.Descrizione as Descrizi2_0_0_ 
FROM Articoli articolo0_ 
left outer join TipiArticolo tipoartico1_ on articolo0_.Tipo=tipoartico1_.ID_TipoArticolo
 WHERE 
articolo0_.ID_Articolo=@p0'
,N'@p0 uniqueidentifier',@p0='B3D1E0B6-295D-452E-A834-4598052EA9C6'

Tutto ciò va benone nel caso in cui le entity coinvolte siano poche, ma se Articolo avesse altre relazioni il calo di performance potrebbe essere pesante, intanto perché parecchi join non fanno bene alla velocità e poi perché magari stiamo recuperando delle informazioni che in un certo contesto non ci interessano affatto.

Cosa fare allora? Per prima cosa modifichiamo leggermente il mapping di TipoArticolo per dire a NHibernate che tutte le istanze di TipoArticolo dovranno supportare il LazyLoad:

<class name="NHLazyTest.TipoArticolo, NHLazyTest" 
    
table="TipiArticolo" lazy="true">

Ora chiediamoci: come viene materialmente realizzato un LazyLoad? Beh, si crea un proxy della classe che, al primo accesso ad uno qualsiasi dei suoi membri, effettua il vero e proprio caricamento. Affinché però tutto funzioni, è necessario

  • che il tipo in oggetto abbia proprietà e metodi dichiarati virtual o, in alternativa,
  • utilizzare un'interfaccia in luogo di un tipo specifico, alla stessa stregua di come accade per le collection.

Nel primo caso non dobbiamo far altro che adattare un po' il codice del domain, pena un'eccezione in fase di creazione della SessionFactory, nel secondo possiamo utilizzare l'attributo proxy per specificare l'interfaccia da utilizzare. In entrambi i casi, comunque, una volta ricompilato il programma, eseguendo lo stesso snippet di prima, NHibernate esegue sul DB una select parecchio più semplice che, come si nota, coinvolge la sola tabella Articoli:

exec sp_executesql N'SELECT articolo0_.ID_Articolo as ID1_0_, articolo0_.Tipo as Tipo1_0_,
articolo0_.Descrizione as Descrizi2_1_0_ FROM Articoli articolo0_ WHERE 
articolo0_.ID_Articolo=@p0'
,N'@p0 uniqueidentifier',@p0='A460678E-1611-4394-A7BA-236F6F68DBBA'

Indagando meglio, si può notare che il TipoArticolo ritornato è un'istanza di una classe il cui nome è all'incircaCProxyTypeTipoArticoloNHLazyTest_INHibernateProxy1 e che, all'invocazione di uno qualsiasi dei suoi membri, carica il vero e proprio TipoArticolo, purché l'oggetto sia persistente, cioé collegato ad una Session.

Due ultime considerazioni: innanzi tutto va da sé che tutto ciò è perfettamente integrato con il sistema di caching di NHibernate, il che vuol dire che il seguente snippet di codice

using (ISession session = SessionHelper.GetSession())
{
    
// carico i due articoli dello stesso tipo
    
articolo = session.Get<Articolo>(id);
    articolo1 = session.Get<Articolo>(id1);

    
// questa forza l'eventuale lazy load del TipoArticolo corrispondente
    string s = articolo.Tipo.Descrizione;


    
// questo non dovrebbe eseguire un'altra query perché TipoArticolo 
    // è già stato caricato dalla session (cache di primo livello)
    
string s1 = articolo1.Tipo.Descrizione;
}

per recuperare l'istanza di TipoArticolo esegue una sola query sul DB, perché, come scritto nei commenti, al fetch su articolo1 l'engine si accorge che il medesimo TipoArticolo è già presente nella cache di primo livello, avendolo caricato in seguito all'esecuzione dello statement

string s = articolo.Tipo.Descrizione;

Inoltre se avessimo voluto comunque forzare il caricamento diretto di TipoArticolo, a prescindere dalla sua dichiarazione di fetching Lazy, sarebbe stato sufficiente modificare il mapping del many-to-one su Articolo in questo modo

<many-to-one name="Tipo" 
    
class="NHLazyTest.TipoArticolo, NHLazyTest" 
    
fetch="join" />

powered by IMHO 1.3

Print | posted on lunedì 21 agosto 2006 04:17 |

Powered by:
Powered By Subtext Powered By ASP.NET