Crad's .NET Blog

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

[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

Print | posted on lunedì 4 settembre 2006 03:39 |

Powered by:
Powered By Subtext Powered By ASP.NET