2012-03-21 9 views
6

Sto pensando di creare la mia implementazione IUnitOfWork per un livello di persistenza NHibernate.è possibile che si verifichino più transazioni all'interno di una sessione in nhibernate? Ed è una cattiva idea?

Sembra che il modo giusto per fare questo sarebbe di avere il ISession e ITransaction un'istanza nel costruttore, e quindi smaltiti nel distruttore o il metodo Dispose().

Naturalmente, se qualcuno invoca il metodo Save(), poi il ISession sarebbe svuota ei ITransaction sarebbe completo, così dopo aver chiamato Save(), non ci sarebbe una transazione aperta valida per Save() di nuovo ... a meno che non ho commesso il prima transazione e immediatamente ha aperto un'altra, nuova transazione. Ma è una buona idea?

Per quanto riguarda la progettazione, è logico eseguire un'operazione di commit, ma non avrò necessariamente il controllo del codice e altri sviluppatori potrebbero essere meno disciplinati riguardo al pattern UnitOfWork.

Perdo/guadagno qualcosa cercando di rendere tollerante il funzionamento di UnitOfWork su più transazioni per sessione? Devo controllare una transazione aperta e lanciare un'eccezione se è già stata commessa, piuttosto che effettuare una nuova transazione?

+0

App Web o desktop? – Phill

+0

È stato progettato per un'applicazione Web. –

risposta

11

Per rispondere alla prima domanda: sì è possibile avere più transazioni in una sessione.

È una buona idea? Dipende.

Il problema è che la modifica dei dati nella prima transazione viene eseguita, mentre non è sicuro se l'intera unità di lavoro (sessione) viene impegnata alla fine. Quando ottieni, diciamo, una StaleObjectException in una transazione successiva, hai già commesso alcuni dati. Nota che questo tipo di eccezione rende la tua sessione inutilizzabile e devi comunque distruggerla. Quindi è difficile ricominciare e riprovare.

direi, funziona bene in queste circostanze:

  • Si tratta di un'applicazione di interfaccia utente
  • modifiche sono lavata solo l'ultima transazione.

UI Application

Gli errori vengono gestiti dall'utente in modo interattivo.Ciò significa che l'utente può vedere ciò che è effettivamente memorizzato in caso di errore e ripete le modifiche apportate.

modifiche sono lavata solo l'ultima transazione

La sessione come attuato da NH vampate solo le modifiche alla fine o "quando necessario". Quindi sarebbe possibile mantenere le modifiche in memoria fino a quando la sessione non viene commessa. Il problema è che NH ha bisogno di svuotare la sessione prima di ogni query, che è difficile da controllare. Può essere disattivato, il che porta ad effetti collaterali. Durante la scrittura di transazioni semplici, potresti averlo sotto controllo. In un sistema complesso è praticamente impossibile assicurarsi che nulla stia andando storto.

il modo più semplice (tm)

ho scritto strato di persistenza di una piuttosto grande sistema client-server. In un sistema del genere, non si hanno errori di gestione degli utenti direttamente. È necessario gestire gli errori nel sistema e restituire il controllo al client in uno stato coerente.

Ho semplificato l'intera gestione della transazione al minimo indispensabile, per renderlo stabile e "a prova di idiota". Ho sempre una sessione e una transazione create insieme e viene commesso o meno.

+0

La mia comprensione è che l'invocazione di 'ITransaction.Commit()' svuota la sessione. Istanziando un nuovo 'ITransaction' usando la stessa sessione, e immediatamente seguendo' Commit() ', quali circostanze genererebbero una' StaleObjectException'? –

+0

Il commit della transazione rilascia i blocchi. Ciò rende più probabile l'acquisizione di StaleObjectExceptions. Ma puoi ottenerli comunque, perché i dati possono cambiare nel db mentre lo stai modificando in memoria. Il blocco ottimistico di NH rende possibile almeno rilevare questi conflitti. –

+1

Grazie per la risposta dettagliata. –

2

Sono disponibili più opzioni per implementare la transazione nidificata di nibernazione con l'unità di lavoro.

Qui sto usando Command pattern per unità di lavoro.

public interface IAction 
{  
    void Execute(); 
} 

public abstract class Action<T> : IAction, IDisposable where T : Action<T> 
{ 
    public void Execute() 
    { 
     try 
     { 
      //Start unit of work by your unit of work pattern or 
      transaction.Begin(); 
      OnExecute(); 
      //End Unit of work 
      transaction.Commit(); 
     } 
     catch (Exception e) 
     { 
      transaction.Rollback(); 
      throw e; 
     } 
    } 

    protected abstract void OnExecute(); 

    public void Dispose() 
    { 

    } 
} 

public class MyBusinessLogic : Action<MyBusinessLogic> 
{ 
    protected override void OnExecute() 
    { 
     //Implementation 
    } 
} 

public class MyAnotherBusinessLogic : Action<MyAnotherBusinessLogic> 
{ 
    protected override void OnExecute() 
    { 
     //Nested transaction 
     MyBusinessLogic logic = new MyBusinessLogic(); 
     logic.Execute(); 
    } 
} 
+0

Solo per segnalare alcune cose: 1) C'è già un System.Action in modo da ottenere confusione sul nome. 2) La tua classe Action non definisce da dove proviene la transazione. 3) Ciò causerà comunque gli stessi errori provocati dalle transazioni nidificate in NHibernate (il commit della transazione interna farà sì che la transazione esterna lanci una ObjectDisposedException). – rossisdead

0

Penso che la soluzione con una transazione per unità di lavoro sia troppo restrittiva. In alcuni ambienti è necessario avere la possibilità di eseguire più transazioni per sessione. Io stesso gestisco le transazioni in modo esplicito e sembra essere una soluzione flessibile.

public interface IUnitOfWork: IDisposable 
{ 
    IGenericTransaction BeginTransaction(); 
} 

public interface IGenericTransaction: IDisposable 
{ 
    void Commit(); 

    void Rollback(); 
} 

public class NhUnitOfWork: IUnitOfWork 
{ 
    private readonly ISession _session; 

    public ISession Session 
    { 
     get { return _session; } 
    } 

    public NhUnitOfWork(ISession session) 
    { 
     _session = session; 
    } 

    public IGenericTransaction BeginTransaction() 
    { 
     return new NhTransaction(_session.BeginTransaction()); 
    } 

    public void Dispose() 
    { 
     _session.Dispose(); 
    } 
} 

public class NhTransaction: IGenericTransaction 
{ 
    private readonly ITransaction _transaction; 

    public NhTransaction(ITransaction transaction) 
    { 
     _transaction = transaction; 
    } 

    public void Commit() 
    { 
     _transaction.Commit(); 
    } 

    public void Rollback() 
    { 
     _transaction.Rollback(); 
    } 

    public void Dispose() 
    { 
     _transaction.Dispose(); 
    } 
} 

L'utilizzo è simile a questo. È facilmente incorporato in qualsiasi modello.

public void Do(IUnitOfWork uow) 
{ 
    using (var tx = uow.BeginTransaction()) { 
    // DAL calls 
    tx.Commit(); 
    } 
} 
+0

Questo è esattamente quello che facciamo anche noi ma non funziona in questo caso: non è possibile modificare il codice in un commento Immagino ... Ma se hai un'altra transazione lì dentro, non funziona. Ma dovrebbe. Le transazioni nidificate sono implementate nei database per una buona ragione. – user2415376

Problemi correlati