2009-10-05 26 views
5

Próbuję znaleźć najlepsze rozwiązanie do obsługi transakcji w aplikacji sieciowej, która używa NHibernate.NHibernate, transakcje i TransactionScope

Używamy modułu IHttpModule i przy HttpApplication.BeginRequest otwieramy nową sesję i wiążemy ją z HttpContext za pomocą ManagedWebSessionContext.Bind (context, session); Zamykamy i rozpakowujemy sesję w HttpApplication.EndRequest.

W naszym repozytorium klasy podstawowej, zawsze owinięty transakcję wokół naszego SaveOrUpdate, Usuń, Zapisz metod, takich jak, według best practice:

 public virtual void Save(T entity) 
     { 
      var session = DependencyManager.Resolve<ISession>(); 
      using (var transaction = session.BeginTransaction()) 
      { 
      session.SaveOrUpdate(entity); 
      transaction.Commit(); 
      } 
     } 

Ale to nie zadziała, jeśli trzeba umieścić transakcja gdzieś w np Usługa aplikacji do włączenia kilku wywołań repozytoriów do Zapisz, Usuń itp.

Staraliśmy się więc użyć TransactionScope (nie chciałem pisać własnego menedżera transakcji). Aby sprawdzić, czy to działa, używam zewnętrzną TransactionScope że nie wymaga .Complete(), aby wymusić wycofywania:

Repository Zapisz():

public virtual void Save(T entity) 
    { 
     using (TransactionScope scope = new TransactionScope()) 
     { 
      var session = DependencyManager.Resolve<ISession>(); 
      session.SaveOrUpdate(entity); 
      scope.Complete(); 
     } 
    } 

Blok, który korzysta z repozytorium :

 TestEntity testEntity = new TestEntity { Text = "Test1" }; 
     ITestRepository testRepository = DependencyManager.Resolve<ITestRepository>(); 

     testRepository.Save(testEntity); 

     using (var scope = new TransactionScope()) 
     { 
      TestEntity entityToChange = testRepository.GetById(testEntity.Id); 

      entityToChange.Text = "TestChanged"; 
      testRepository.Save(entityToChange); 
     } 

     TestEntity entityChanged = testRepository.GetById(testEntity.Id); 

     Assert.That(entityChanged.Text, Is.EqualTo("Test1")); 

To nie działa. Ale dla mnie, jeśli NHibernate obsługuje TransactionScope, będzie! Co się dzieje, że w bazie danych nie ma wcale ROLLBACK, ale kiedy testRepository.GetById (testEntity.Id); Instrukcja wykonywana jest UPDATE z SET Text = "TestCahgned" jest uruchamiany zamiast (Powinien zostać uruchomiony pomiędzy BEGIN TRAN a ROLLBACK TRAN). NHibernate odczytuje wartość z pamięci podręcznej poziomu 1 i uruchamia aktualizację do bazy danych. Nie oczekiwane zachowanie !? Z tego co rozumiem za każdym razem, gdy wykonywane jest wycofywanie w zakresie NHibernate, musisz również zamknąć i usunąć powiązanie z bieżącą sesją.

Moje pytanie brzmi: Czy ktokolwiek wie o dobrym sposobie wykonania tego przy użyciu TransactionScope i ManagedWebSessionContext?

+1

Jeśli korzystasz z TransactionScope, musisz użyć NHibernate 2.1. Dopiero w wersji 2.1 NH uzyskał dobrą integrację z TransactionScope. –

Odpowiedz

2

Podjęłam bardzo podobne podejście. W module HttpModule pytam o sesję dotyczącą nowej sesji + wiążę ją, gdy pojawi się nowe żądanie. Ale ja również rozpoczynam tutaj transakcję. Następnie, gdy żądanie się kończy, po prostu go usuwam i podejmuję próbę zatwierdzenia transakcji.

Również moje podstawowe repozytorium nie bierze żadnej sesji - zamiast tego poprosi o bieżącą sesję, a następnie wykona pracę z sesją. Również nie zawijam niczego wewnątrz tej klasy bazowej za pomocą transakcji. Zamiast tego całe żądanie http jest pojedynczą jednostką pracy.

Może to nie być odpowiednie dla projektu, nad którym pracujesz, ale ja wolę takie podejście, ponieważ każde żądanie zakończy się niepowodzeniem lub odniesie sukces jako pojedyncza jednostka atomowa. Mam pełny wpis na blogu here z kodem źródłowym, jeśli jesteś zainteresowany rzeczywistą implementacją.

Poniżej jest przykład co to repozytorium baza wygląda następująco:

public abstract class NHibernateRepository<T> where T : class 
{ 

    protected readonly ISessionBuilder mSessionBuilder; 

    public NHibernateRepository() 
    { 
     mSessionBuilder = SessionBuilderFactory.CurrentBuilder; 
    } 

    public T Retrieve(int id) 
    { 
      ISession session = GetSession(); 

      return session.Get<T>(id); 
    } 

    public void Save(T entity) 
    { 
      ISession session = GetSession(); 

      session.SaveOrUpdate(entity); 
    } 

    public void Delete(T entity) 
    { 
      ISession session = GetSession(); 

      session.Delete(entity); 
    } 

    public IQueryable<T> RetrieveAll() 
    { 
      ISession session = GetSession(); 

      var query = from Item in session.Linq<T>() select Item; 

      return query; 
    } 

    protected virtual ISession GetSession() 
    { 
     return mSessionBuilder.CurrentSession; 
    } 
} 
1

Dzięki za odpowiedź!

Tak, to prosty i prosty sposób, aby go rozwiązać.Ale moim problemem jest to, że chcę upewnić się, że istnieje transakcja otaczająca operację repozytorium, nawet jeśli usługa aplikacji, repozytorium itp. Nie jest wywoływana przez żądanie WWW (inne typy klientów), dlatego chciałem mieć transakcję wokół najniższy poziom (np. session.Save), a następnie użyj TransactionScope, aby w razie potrzeby utworzyć dłuższą transakcję. Ale twoje rozwiązanie jest proste i podoba mi się to, mabye wykorzystam to, a następnie upewnię się, że inni klienci również używają transakcji.

+0

, więc używasz tej "usługi" w kontekście WCF/ASMX, czy jest to domena jak usługa wewnątrz twojej aplikacji internetowej? –

1

Cykl życia transakcja powinna być:

using (TransactionScope tx = new TransactionScope()) 
{ 
    using (ISession session1 = ...) 
    using (ITransaction tx1 = session.BeginTransaction()) 
    { 
    ...do work with session 
    tx1.Commit(); 
    } 

    using (ISession session2 = ...) 
    using (ITransaction tx2 = session.BeginTransaction()) 
    { 
    ...do work with session 
    tx2.Commit(); 
    } 

    tx.Complete(); 
} 
+1

Dobry przykład. Skopiowałem twój kod w mojej odpowiedzi na "http://stackoverflow.com/a/41255520/5779732". Wspomniałem również twoje nazwisko i link do tej odpowiedzi. –

1

Rzeczywiście można sprawdzić, czy dana transakcja jest aktywna przy użyciu: Session.Transaction.IsActive. Jeśli nie jesteś aktywny, możesz go utworzyć. Można również utworzyć metodę Transact, która robi to automatycznie dla większości z nich. Oto fragment, który jest głównie z NHibernate 3.0 Cookbook:

// based on NHibernate 3.0 Cookbook, Data Access Layer, pg. 192 
public class GenericDataAccessObject<TId> : IGenericDataAccessObject<TId> 
{ 
    // if you don't want to new up your DAO per Unit-of-work you can 
    // resolve the session at the time it's accessed. 
    private readonly ISession session; 

    protected GenericDataAccessObject(ISession session) 
    { 
     this.session = session; 
    } 

    protected ISession Session { get { return session; } } 

    public virtual T Get<T>(TId id) 
    { 
     return Transact(() => Session.Get<T>(id)); 
    } 

    protected virtual void Save<T>(T entity) 
    { 
     Transact(() => Session.Save(entity)); 
    } 

    /// <summary> 
    /// Perform func within a transaction block, creating a new active transaction 
    /// when necessary. No error handling is performed as this function doesn't have 
    /// sufficient information to provide a useful error message. 
    /// </summary> 
    /// <typeparam name="TResult">The return type</typeparam> 
    /// <param name="func">The function wrapping the db operations</param> 
    /// <returns>The results returned by <c>func</c></returns> 
    protected TResult Transact<TResult>(Func<TResult> func) 
    { 
     // the null Transaction shouldn't happen in a well-behaving Session 
     // implementation 
     if (Session.Transaction == null || !Session.Transaction.IsActive) 
     { 
      TResult result; 

      // transaction rollback happens during dispose when necessary 
      using (var tx = Session.BeginTransaction()) 
      { 
       result = func.Invoke(); 
       tx.Commit(); 
      } 
      return result; 

      // We purposefully don't catch any exceptions as if we were to catch 
      // the error at this point we wouldn't have enough information to describe 
      // to the user why it happened -- we could only describe what happened. 
     } 
     return func.Invoke(); 
    } 

    protected void Transact(Action action) 
    { 
     Transact<bool>(() => 
          { 
           action.Invoke(); 
           return false; 
          } 
      ); 
    } 
}