2010-08-26 10 views
5

Primo, un po 'di background: sono nuovo di ASP.NET MVC 2 e NHibernate. Sto iniziando la mia prima applicazione e voglio utilizzare NHibernate, perché provengo da applicazioni web JSP + Struts 1 + Hibernate.Quando eseguire il commit delle transazioni NHibernate nell'applicazione ASP.NET MVC 2?

Nessuno sembra parlare di questo, quindi immagino che debba essere abbastanza ovvio. Ancora mi gratto la testa perché non riesco a trovare una soluzione che compia le seguenti cose:

1) Voglio usare la strategia "sessione per richiesta". Quindi, ogni volta che un utente fa una richiesta, ottiene una sessione di Nhibernate, avvia una transazione e quando la richiesta è finita, la transazione si impegna e la sessione di NHibernate si chiude (e torna al pool se ce n'è una). Questo garantisce che le mie transazioni siano atomiche.

2) Quando si verifica un'eccezione del database (violazione PK, violazione univoca, qualsiasi cosa) Voglio catturare quell'eccezione, eseguire il rollback della transazione e fornire all'utente un messaggio esplicito: se si trattava di violazione PK, quel messaggio e il messaggio lo stesso con tutti gli errori di integrità.

Quindi, qual è il mio problema? Vengo da Java World, dove ho usato un filtro per aprire la sessione, avviare la transazione, elaborare la richiesta, quindi eseguire il commit della transazione e chiudere la sessione. Funziona, tranne quando si verifica un'eccezione DB e, quando si è nel filtro, non è possibile modificare la pagina di destinazione perché la risposta è già stata eseguita.

Così l'utente vede la pagina di successo quando in realtà la transazione è stata sottoposta a rollback. Per evitare ciò, devo scrivere molti controlli di integrità dei dati in Java per evitare tutte le eccezioni di integrità, perché non sono riuscito a gestirli correttamente. Questo è male perché sto facendo il lavoro invece di lasciarlo nel database (o forse ho sbagliato e devo sempre scrivere tutto questo codice di integrità dei dati nella mia app?).

Quindi ho trovato l'interfaccia IHttpModule che sto indovinando è praticamente lo stesso concetto di un javax.servlet.Filter (correggimi se sbaglio), quindi immagino di poter avere lo stesso problema di nuovo.

Dove devo inserire i miei commit per assicurarmi che le mie transazioni siano atomiche e quando generano eccezioni posso catturarle e cambiare la mia pagina di destinazione e dare all'utente un messaggio completo?

Finora l'unica soluzione possibile che ho trovato è quella di mantenere il mio IHttpModule per avviare e chiudere la transazione e inserire le chiamate di commit nell'ultima riga dei miei metodi di controller, in modo da poter acquisire eccezioni lì e quindi restituire una vista appropriata con il messaggio. Ora dovrei copiare quelle linee di gestione commit ed eccezione in tutti i miei metodi controller che richiedono commit. E c'è il problema della separazione delle preoccupazioni, che i miei controllori devono sapere su DB, che non mi piace affatto.

C'è un modo migliore?

risposta

0

Beh, dopo averci pensato e discusso con i colleghi mi è venuta in mente una soluzione che soddisfa quasi tutte le mie esigenze .

Ho implementato la soluzione con i miei progetti Java e ha funzionato benissimo. Mi limiterò a rovinare l'idea in modo che tutti possano usarla in qualsiasi contesto.

La soluzione consiste nel mettere la chiamata di commit nell'ultima riga del metodo controller, all'interno di un blocco try-catch. Se si verifica un'eccezione di vincolo, è possibile ottenere il nome del vincolo violato. Con il nome puoi dire all'utente esattamente cosa è andato storto. Ho usato un file delle proprietà per archiviare il messaggio da mostrare all'utente in cui è stato violato il vincolo. Le chiavi del file delle proprietà sono i nomi dei vincoli ei valori sono i messaggi di violazione dei vincoli.

Yo può refactificare il commit-handle_exception-find_constraint_message con un metodo, questo è quello che ho fatto.

Per ora risolve il mio problema di scrittura del codice per verificare l'integrità del database e credo che sia piuttosto elegante con i messaggi di violazione dei vincoli in un file delle proprietà. Ora, non mi piace ancora l'idea che i miei controller debbano chiamare il commit, ma è molto meglio che scrivere integrità controlla che il database già sia.

Continuerò ad usare un filtro proprio come ha detto David Kemp, solo che il filtro aprirà solo la sessione (n) di ibernazione e la transazione, e quindi, al termine della richiesta, chiuderà la sessione.

I commenti sono più che benvenuti. Grazie.

+0

Puoi mostrare un esempio nel codice? Sarebbe utile. –

1

Se si utilizza ASP.NET MVC, è possibile utilizzare uno ActionFilter per ottenere lo stesso effetto.

Qualcosa di simile (questo è messo insieme da pezzi di differenza di mia architettura):

public class TransactionalAttribute : ActionFilterAttribute, IAuthorizationFilter, IExceptionFilter 
{ 

    ITransaction transaction = NullTransaction.Instance; 
    public IsolationLevel IsolationLevel { get; set; } 

    public TransactionalAttribute() 
    { 
     IsolationLevel = IsolationLevel.ReadCommitted; 
    } 

    public override void OnResultExecuted(ResultExecutedContext filterContext) 
    { 
     try 
     { 
      transaction.Commit(); 
      transaction = NullTransaction.Instance; 
     } 
     catch (Exception exception) 
     { 
      Log.For(this).FatalFormat("Problem trying to commit transaction {0}", exception); 
     } 

    } 

    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     if (transaction == NullTransaction.Instance) transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel); 
    } 

    public override void OnActionExecuted(ActionExecutedContext filterContext) 
    { 
     if (filterContext.Result != null) return; 

     transaction.Commit(); 
     transaction = NullTransaction.Instance; 
    } 

    public void OnAuthorization(AuthorizationContext filterContext) 
    { 
     transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel); 
    } 

    public void OnException(ExceptionContext filterContext) 
    { 
     try 
     { 
      transaction.Rollback(); 
      transaction = NullTransaction.Instance; 
     } 
     catch (Exception exception) 
     { 
      Log.For(this).FatalFormat("Problem trying to rollback transaction {0}", exception); 
     } 
    } 

    private class NullTransaction : ITransaction 
    { 
     public static ITransaction Instance { get { return Singleton<NullTransaction>.Instance; } } 

     public void Dispose() 
     { 

     } 

     public void Commit() 
     { 
     } 

     public void Rollback() 
     { 
     } 
    } 
} 
Problemi correlati