NHibernate
[NHibernate] Mapping di un composite-id

Alle volte (anzi, molto spesso) nel realizzare la nostra applicazione non abbiamo tutta la libertà di questo mondo, magari perché lo schema DB ci viene "imposto" da un'altra figura professionale. In questi casi non è raro trovarsi ad avere a che fare con chiavi primarie composte, che si estendono cioé su più colonne. Con NHibernate possiamo gestire egregiamente anche situazioni di questo tipo, anche se è bene sottolineare che le cose si complicano un pochino quindi, nel caso in cui si debba partire da zero, è sempre meglio utilizzare la solita surrogate key, semplice, invariante e priva di significato.

Bene, supponiamo di avere il seguente schema:

concentriamoci per il momento esclusivamente sull'oggetto fattura. In prima approssimazione, una sua semplice rappresentazione nel domain model potrebbe essere del tipo:

NHibernate, come sappiamo, identifica gli oggetti grazie al loro identityField. Affinché tutto continui a funzionare anche nel caso in cui esso non sia uno scalare, ma un tipo complesso, è necessario effettuare l'override dei metodi Equals() e GetHashCode(); in caso contrario, infatti, l'engine di persistenza non sarebbe in grado di determinare quando due oggetti si riferiscono alla medesima riga di database (e quindi, ad es., gestire correttamente l'identity map della sessione, dato che questo altri non è che un dictionary). Allora, per isolare il concetto di id della fattura dalla fattura stessa, può essere conveniente creare una classe ad-hoc:

Una piccola nota: è di estrema importanza ridefinire correttamente anche GetHashCode(), e per correttamente intendo dire "in modo coerente a Equals", cioé che due oggetti uguali restituiscano hash uguali, mentre due oggetti diversi restituiscano hash diversi. A volte mi è capitato di vedere

public override int GetHashCode()
{
    
return base.GetHashCode();
}

Bene, sappiate che quanto scritto qui sopra è il modo migliore per far sì che subdoli bachi intervengano a turbare le vostre notti.

Torniamo a noi. Come scriviamo il mapping di un domain del genere? IdFattura per NHibernate non è una entity, bensì un component, vale a dire un value type (attenzione, non nel senso di .Net) il cui lifetime coincide con quello della entity a cui esso è collegato e che ha senso solo quando collegato alla entity stessa. Definizione e mapping sulle colonne, pertanto, si trovano all'interno del nodo class della entity Fattura:

<class name="Fattura" table="Fatture">
    <composite-id 
name="Id" class="IdFattura">
        <key-property 
name="Numero" />
        <key-property 
name="Anno" />
    <
/composite-id>
    
...
</class>

Ovviamente, per ogni nodo <key-property> ho omesso di specificare anche la relativa column, visto che è omonima della proprietà a cui si riferisce. A questo punto il gioco è fatto, se vogliamo inserire una nuova fattura possiamo scrivere

Fattura f = new Fattura();
f.Id.Numero = 5;
f.Id.Anno = 2006;
f.Cliente = "Pippo";

session.Save(f);
session.Flush();

oppure tramite questo snippet

IdFattura id = new IdFattura();
id.Numero = 5;
id.Anno = 2006;
Fattura f1 = session.Get<Fattura>(id);

NHibernate saprà recuperare l'oggetto voluto proprio come nel caso di una chiave scalare. Ah, una nota, visto qualche giorno fa son stati espressi dubbi in proposito: con un'implementazione del genere, NH non reidrata l'oggetto id, che anzi viene assegnato alla proprietà Fattura.Id; in pratica, il test dell'uguaglianza di riferimento

Debug.Assert(id == f1.Id);

NON fallisce.

Per quanto riguarda il dettaglio, si può seguire lo stesso approccio, definendo quindi una classe IdDettaglio; dato che, però, la coppia di colonne Numero e Anno identificano univocamente una fattura, perché non includere il riferimento all'istanza della fattura proprio all'interno di questo identityField, piuttosto che portarci dietro la semplice chiave? Completiamo allora il nostro domain model includendo le nuove classi relative al dettaglio e inserendo una collection di dettagli all'interno di Fattura, secondo il seguente diagramma:

Anche per IdDettaglio vale quanto detto a proposito di IdFattura, vale a dire che

  • è necessario ridefinire Equals() e GetHashCode()
  • per NH non si tratta di una nuova entity ma "solo" di un component
  • come tale, il suo lifetime è il medesimo della particolare istanza a cui si riferisce.

L'unica differenza, sta nel fatto che dobbiamo descrivere, all'interno del file di mapping, la relazione many-to-one intrinseca nella chiave; lo facciamo con il seguente xml:

<class name="Dettaglio" table="DettagliFattura">
    <composite-id 
name="Id" class="IdDettaglio">
        <key-many-to-one 
class="Fattura" name="Fattura">
            <column 
name="Numero"/>
            <column 
name="Anno"/>
        <
/key-many-to-one>
        <key-property 
name="Progressivo" />
    <
/composite-id>    
    
...
</class>

Ovviamente, non sarà necessario aggiungere un nodo <many-to-one> che punti ad una fattura a livello di entity Dettaglio, dato che questo è già incluso nella chiave ed automaticamente risolto da NHibernate. Vogliamo rendere la relazione bidirezionale? dal lato della fattura possiamo includere una bag, che a sua volta avrà una composite-key al suo interno (visto che Fattura è identificata da una composite-key):

<class name="Fattura" table="Fatture">
    
...
    
<bag name="Dettagli" generic="true" inverse="true" cascade="none" >
        <key>
            <column 
name="Numero" />
            <column 
name="Anno" />
        <
/key>
        <one-to-many 
class="Dettaglio" />
    <
/bag>
<
/class>

Un'ultima nota: queste chiavi composte, essendo chiavi naturali, non possono che essere assigned. Questo vuol dire che il valore deve essere impostato prima che esse siano agganciate ad una session e, quindi, NHibernate non può in alcun modo discriminare un oggetto transient da uno detachied; quindi possiamo scordarci tanto il

session.SaveOrUpdate();

che un qualsivoglia cascade all'interno della bag di cui sopra. In un prossimo articolo, quando parlerò del versioning, spiegherò come è possibile ovviare al problema.

La solita piccola applicazione di esempio è scaricabile a questo link.

Alla prossima!

powered by IMHO 1.3

[NHibernate] IInterceptor per personalizzare il comportamento della Session

Nonostante l'oggetto Session sia privo di eventi, NHibernate offre comunque un supporto a chi voglia realizzare dei metodi di Callback implementando il pattern Observer tramite un oggetto particolare denominato Interceptor. Esso non è altro che una classe che implementa l'interfaccia IInterceptor, la quale espone alcuni metodi che vengono richiamati dalla Session in corrispondenza delle operazioni di Load, Save, Flush, ecc... L'associazione di un Interceptor ad una Session avviene all'atto della sua creazione, tramite un particolare overload del metodo SessionFactory.CreateSession :

ISession mySession = SessionFactory.CreateSession(myInterceptor);

Come potrete immaginare, un oggetto del genere si presta ad innumerevoli applicazioni pratiche, una delle quali (diciamo anche quella che più facilmente viene in mente) è la realizzazione di un servizio di audit, che tenga traccia di data e utente relativi alla creazione e alla modifica di un oggetto del nostro dominio. Per far ciò, definiamo innanzitutto la seguente interfaccia:

public interface IAuditable
{
    DateTime DataCreazione { 
get; }
    
string UtenteCreazione { get; }
    DateTime DataModifica { 
get; }
    
string UtenteModifica { get; }
}

Tramite NHibernate è possibile persistere automaticamente queste informazioni in ogni classe che implementi questa interfaccia, senza preoccuparcene minimamente all'interno del codice di business.

Il modo più elegante per riuscirci è realizzare una classe che implementi IInterceptor o, più semplicemente, ereditare dalla classe EmptyInterceptor (che altri non è che un Interceptor completamente vuoto), effettuando l'override dei metodi utili al nostro scopo. OnFlushDirty, ad esempio, è sicuramente uno di questi:

public override bool OnFlushDirty(object entity, object id, object[] currentState, 
    
object[] previousState, string[] propertyNames, IType[] types)

Esso viene invocato in corrispondenza del Flush di ogni entity marcata come dirty (cioé che ha subito modifiche dall'operazione di load) e fornisce una serie di parametri per accedere ad una reference alla stessa, conoscerne il suo stato precendente ed eventualmente apportarvi variazioni prima dell'update (agendo sull'array currentState, entity e previousState non dovrebbero mai essere modificati direttamente).

Nel nostro caso, ad esempio, si può scrivere:

public override bool OnFlushDirty(object entity, object id, 
    
object[] currentState, object[] previousState, 
    
string[] propertyNames, IType[] types)
{
    IAuditable auditableEntity = entity 
as IAuditable;
    
if (auditableEntity == null)
        
return base.OnFlushDirty(entity, id, 
        currentState, previousState, propertyNames, types);

    
bool res = false;
    
for (int i = 0; i < propertyNames.Length - 1; i++)
    {
        
if (propertyNames[i] == "DataModifica")
        {
            currentState[i] = DateTime.Now;
            res = 
true;
        }
        
if (propertyNames[i] == "UtenteModifica")
        {
            currentState[i] = username;
            res = 
true;
        }
    }
    
    
return res;
}

Dopo aver verificato che la entity corrente implementi l'interfaccia IAuditable, scorriamo l'array propertyNames alla ricerca delle proprietà "DataModifica" e "UtenteModifica". Una volta trovate, modifichiamo il corrispondente valore nell'array currentState ed impostiamo il valore da restituire a true. Così facendo, NHibernate rifletterà tali modifiche tanto nell'oggetto in memoria che nella query che sarà inviata al database. Una cosa importante da notare è che ciò è possibile nonostante le proprietà di IAuditable siano marcate come Readonly: avendo infatti scritto nel file di mapping

<hibernate-mapping ..... default-access="field.camelcase">
<
/hibernate-mapping>

sarà NH stesso a preoccuparsi di utilizzare la strategy selezionata e quindi di utilizzare i corrispondenti fields.

Ovviamente l''interfaccia IInterceptor mette a disposizione parecchi altri metodi per personalizzare il comportamento standard di NHibernate e plasmarlo secondo le nostre esigenze; alcuni di essi sono molto interessanti, parlo ad esempio di

  • FindDirty, che permette di determinare se una entity è da considerarsi dirty o meno, in modo che NHibernate possa eventualmente effettuarne la query di update in fase di flush;
  • Instantiate, che consente di costruire "a mano" una nuova istanza di un'oggetto dopo che i dati ad esso associati siano stati recuperati dal database;
  • IsUnsaved, che lascia all'utente la facoltà di discriminare, in corrispondenza di un SaveOrUpdate, se una entity è transiente o detached, in modo da eseguire rispettivamente una Save o una Update.

Nel mio caso, proprio quest'ultimo è stato di notevole aiuto per risolvere una problematica relativa all'utilizzo di chiavi primarie di tipo assigned, per le quali cioé non è possibile stabilire un unsaved-value, ma scriverò prossimamente un post specifico a riguardo. Purtroppo la documentazione sull'interceptor è piuttosto scarna, sia nella reference di NHibernate che su quella di Hibernate3. Per chi volesse dare un'occhiata approfondita alle API di quest'interfaccia consiglio la Hibernate API Documentation, relativa alla versione 3.1 per Java ma applicabile con un po' d'intuito anche al mondo .NET.

Anche per quest'articolo, come al solito, ho preparato una piccola applicazione d'esempio che può essere scaricata a questo link.

Alla prossima! 

Update: come mi fa giustamente notare Tommaso, EmptyInterceptor non esiste su NHibenate 1.0.2 o, meglio, esiste nel namespace NHibernate.Cfg ma è internal. L'esempio che ho fornito compila perfettamente perché è basato sulla versione 1.2.0 Alpha. BTW, quanto detto in questo articolo continua a valere anche per le versioni precedenti di NHibernate, a patto che si implementi direttamente IInterceptor. Chi volesse realizzare una propria EmptyInterceptor può guardare i sorgenti di NHibernate per avere un'idea del codice (veramente banale) di questo implementor.

powered by IMHO 1.3

[NHibernate] Inheritance mapping

Ereditarietà e polimorfismo sono due caratteristiche della programmazione OO estremamente potenti, che possono essere sfruttate anche nella costruzione di domini complessi. La persistenza su un DBMS di queste strutture, però, pone dei problemi, in quanto non sono direttamente mappabili su un modello ER.

Supponiamo di dover persistere su DBMS il seguente dominio:

Come realizzare lo schema del DB? Beh, una trattazione completa dell'argomento è decisamente eccessiva, per cui si rimanda ad altre fonti, a noi basta semplicemente sapere che le scelte più usate ricadono tra:

  • Table per class hierarchy, in cui una sola tabella contiene una colonna per ogni attributo di qualsiasi classe della gerarchia, e quindi è capace di contenere tutta la gerarchia di oggetti; i diversi tipi sono distinti dal valore di una particolare colonna, chiamata discriminatore;
  • Table per subclass, in cui si crea una tabella per ogni oggetto di qualsiasi livello, le tabelle degli oggetti figlio contengono una FK verso le tabelle relative agli oggetti padre;
  • Table per concrete class, in cui si crea una tabella per ogni oggetto figlio, contenente tutte le proprietà dello stesso e dell'oggetto padre.

Il terzo caso pone anche alcune problematiche nel caso in cui esistano delle reference (da tradurre in relazioni su DB) tra altre entity e un oggetto padre (es. Persona -> Veicolo posseduto: sulla tabella Persone che codice metto? ID_Motociclo? ID_Automobile? entrambi?); inoltre, non è direttamente mappabile in NHibernate (lo è su Hibernate per Java dalla versione 3 in poi, credo) e per questa ragione lo trascureremo. Ci concentreremo, allora, sui primi due casi.

Table per class hierarchy

In questo caso è necessario utilizzare nel file di mapping il nodo Discriminator, che accetta un attributo Column per indicare il nome della colonna utilizzata per differenziare i vari tipi. La classe padre (e le classi figlio, mappate tramite l'elemento Subclass), presentano un attributo che indica il valore del discriminatore associato al tipo stesso:

<class name="NHSubclassTest.Veicolo, NHSubclassTest" 
    
table="CLASS_Veicoli">
    
<!-- ... -->

    
<discriminator column="TipoVeicolo" />
    
<!-- properties di Veicolo -->

    
<subclass name="NHSubclassTest.Automobile, NHSubclassTest" 
        
discriminator-value="A">
        
<!-- properties di Automobile -->
    
</subclass>

    <subclass 
name="NHSubclassTest.Motociclo, NHSubclassTest" 
        
discriminator-value="M">
        
<!-- properties di Motociclo -->
    
</subclass>
<
/class>

Come si può notare, ovviamente, il nome della tabella è specificato solo nella classe padre. Proviamo ad eseguire del codice e vediamo come reagisce NHibernate. Supponiamo di voler memorizzare una entity:

using (ISession session = SessionHelper.GetSession())
{
    Automobile auto = 
new Automobile();
    
// valorizzo le properties di auto
    
session.SaveOrUpdate(auto);
    session.Flush();
}

Come è lecito aspettarsi, NH produrrà una query di inserimento sul DB con il valore del discriminator impostato ad "A", dato che stiamo persistendo un'istanza del tipo Automobile, e con i campi specifici di Motociclo tutti pari a NULL:

exec sp_executesql N'INSERT INTO CLASS_Veicoli (Furgonato, NumeroPorte, Modello, 
Marca, Cilindrata, TipoVeicolo, ID_Veicolo) VALUES (@p0, @p1, @p2, @p3, @p4, ''A'', 
@p5)'
,N'@p0 bit,@p1 int,@p2 nvarchar(4000),@p3 nvarchar(4000),@p4 int,@p5 
uniqueidentifier'
,@p0=1,@p1=3,@p2=N'Punto',@p3=N'Fiat',@p4=1600,
@p5='D96E2365-6E84-4E75-9712-003E5EEADA9C'

Una caratteristica interessante è che possiamo caricare con una Get un generico oggetto di tipo Veicolo, penserà poi NHibernate a costruire, a seconda del discriminator, il tipo concreto corretto. Stesso dicasi se vogliamo recuperare un elenco di veicoli presenti nel database: il metodo ICriteria.List restituirà una IList<Veicolo> con all'interno i tipi concreti memorizzati nel database:

using (ISession session = SessionHelper.GetSession())
{
    
// recupero un elenco di veicoli
    
IList<Veicolo> lista = session
        .CreateCriteria(
typeof(Veicolo))
        .List<Veicolo>();
    
foreach (Veicolo v in lista)
    {
        Console.WriteLine("Veicolo n.{0}, tipo {1}", 
            lista.IndexOf(v), v.GetType().Name);
    }    
}

using (ISession session = SessionHelper.GetSession())
{
    
// corretto caricamento delle istanze
    // uso una session differente per evitare il caching di primo livello
    
Veicolo veicolo = session.Get<Veicolo>(id);
    Debug.Assert(veicolo != 
null
        "Grazie al polimorfismo, recupero una generica istanza di veicolo");
}

Lo svantaggio di questo approccio è quello di dover creare una tabella su DB per sua natura un po' "sporca", visto che effettivamente memorizza informazioni eterogenee che richiederanno, di volta in volta, di valorizzare alcune colonne piuttosto che altre.

Table per subclass

Per utilizzare il secondo approccio, è necessario modificare leggermente il mapping del dominio, utilizzando l'elemento joined-subclass in luogo del precedente:

<class name="NHSubclassTest.Veicolo, NHSubclassTest" 
    
table="SUBCLASS_Veicoli">
    
<!-- proprietà di Veicolo -->

    
<joined-subclass name="NHSubclassTest.Automobile, NHSubclassTest" 
        
table="SUBCLASS_Automobili">
        <key 
column="ID_Automobile" />
        <property 
name="Furgonato" />
        <property 
name="NumeroPorte" />
    <
/joined-subclass>

    <joined-subclass 
name="NHSubclassTest.Motociclo, NHSubclassTest" 
        
table="SUBCLASS_Motocicli">
        <key 
column="ID_Motociclo" />
        <property 
name="Bauletto" />
        <property 
name="TipoForcella" />
    <
/joined-subclass>
<
/class>

NHibernate persisterà le informazioni separatamente, inserendo i valori relativi alle proprietà di ogni classe della gerarchia all'interno della tabella assegnata ed avendo cura che le chiavi primarie coincidano. Lo snippet di codice di creazione di un automobile esposto in precedenza, questa volta produce le seguenti query su DB:

exec sp_executesql N'INSERT INTO SUBCLASS_Veicoli ....'

exec sp_executesql N'INSERT INTO SUBCLASS_Automobili .....'

Come si può notare, questa volta la memorizzazione (e lo stesso dicasi per l'eliminazione e per le modifiche che coinvolgono proprietà di entrambi i livelli) richiede l'esecuzione di due query su database, numero che aumenta di pari passo con la profondità della gerarchia.

L'esecuzione di una Get produce invece un output differente, a seconda che si tratti della ricerca di una classe concreta o di una astratta. Se infatti richiediamo una istanza di Motociclo, infatti, NHibernate sa già che per recuperare tutte le informazioni dovrà effettuare un join tra la tabella dei Veicoli e quella dei Motocicli; quindi, lo snippet seguente

using (ISession session = SessionHelper.GetSession())
{
    Motociclo moto = session.Get<Motociclo>(myId);    
}

produce una query con un join (generalizzando, con un join per ogni livello di profondità della gerarchia ad esclusione del primo):

exec sp_executesql N'SELECT motociclo0_.ID_Motociclo as ID1_0_, ....
FROM SUBCLASS_Motocicli motociclo0_ inner join 
     SUBCLASS_Veicoli motociclo0_1_ on .....'

Nel caso in cui, invece, si recuperi un generico veicolo dato un Id, la situazione si fa più complessa. Lo snippet

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

produce infatti la seguente query

exec sp_executesql N'SELECT ....colonne di 3 tabelle...., 
case 
    when veicolo0_1_.ID_Automobile is not null then 1 
    when veicolo0_2_.ID_Motociclo is not null then 2 
    when veicolo0_.ID_Veicolo is not null then 0 
end ...
FROM     SUBCLASS_Veicoli veicolo0_ left outer join 
        SUBCLASS_Automobili veicolo0_1_ on ... left outer join 
        SUBCLASS_Motocicli veicolo0_2_ on ... '

NHibernate ora è costretto ad ispezionare tutta la struttura di tabelle per capire la tipologia di oggetto che corrisponde ad una determinata riga. Purtroppo questo tipo di interrogazione produce una query le cui dimensioni aumentano molto velocemente (viene introdotto un ulteriore join per ogni entity esistente nella gerarchia, a qualsiasi livello) e quindi rischia di diventare ben presto parecchio onerosa per il DB.

Applicazione di esempio

Come per l'articolo precedente, anche questa volta ho sviluppato una piccola applicazione di esempio scaricabile a questo link. Consiglio di eseguirla con il profiler alla mano, per verificare effettivamente cosa accade sul database. Per provare le differenze tra le due soluzioni implementative, è sufficiente settare la proprietà Build Action del file di mapping voluto come Embedded Resource, avendo cura di impostare invece l'altro come Content.

powered by IMHO 1.3

[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