2009-10-05 6 views
5

J'essaie de trouver la meilleure solution pour gérer les transactions dans une application web qui utilise NHibernate.NHibernate, transactions et TransactionScope

Nous utilisons un IHttpModule et à HttpApplication.BeginRequest nous ouvrons une nouvelle session et nous la lions au HttpContext avec ManagedWebSessionContext.Bind (context, session); Nous fermons et dissocions la session sur HttpApplication.EndRequest.

Dans notre classe de base du référentiel, nous enveloppa toujours une transaction autour de notre saveOrUpdate, Supprimer, Obtenez des méthodes telles que, selon best practice:

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

Mais cela ne fonctionne pas, si vous avez besoin de mettre un transaction quelque part dans par exemple un service d'application pour inclure plusieurs appels de référentiel à enregistrer, supprimer, etc.

Donc, ce que nous avons essayé est d'utiliser TransactionScope (je ne voulais pas écrire mon propre gestionnaire de transaction). Pour vérifier que cela a fonctionné, j'utilise un TransactionScope externe qui ne remet pas .Complete() pour forcer un rollback:

Repository Save():

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

Le bloc qui utilise le référentiel :

 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")); 

Cela ne fonctionne pas. Mais pour moi, si NHibernate prend en charge TransactionScope, il le ferait! Qu'est-ce qui se passe est qu'il n'y a pas de ROLLBACK du tout dans la base de données, mais quand le testRepository.GetById (testEntity.Id); instruction est exécutée une UPDATE avec SET Text = "TestCahgned" est déclenché à la place (il aurait dû être déclenché entre BEGIN TRAN et ROLLBACK TRAN). NHibernate lit la valeur du cache de niveau 1 et envoie une UPDATE à la base de données. Comportement non attendu !? D'après ce que je comprends à chaque fois qu'une restauration est effectuée dans le cadre de NHibernate, vous devez également fermer et dissocier la session en cours.

Ma question est la suivante: Quelqu'un connaît-il un bon moyen de le faire en utilisant TransactionScope et ManagedWebSessionContext?

+1

Si vous utilisez TransactionScope, vous devez utiliser NHibernate 2.1. C'est seulement avec 2.1 que NH a vraiment eu une bonne intégration avec TransactionScope. –

Répondre

2

J'ai pris une approche très similaire. Dans le HttpModule, je demande à la sessionfactory une nouvelle session + la lier quand une nouvelle requête arrive. Mais je commence aussi la transaction ici. Puis, lorsque la requête se termine, je la délie simplement et tente de valider la transaction.

Mon référentiel de base ne prend en aucun cas une session - il demande plutôt la session en cours et effectue ensuite un travail avec la session. De plus, je n'emballe rien dans cette classe de base avec une transaction. Au lieu de cela, la requête http entière est une seule unité de travail.

Cela peut ne pas convenir au projet sur lequel vous travaillez, mais je préfère cette approche car chaque requête échouera ou réussira en tant qu'unité atomique unique. J'ai un blog complet here avec le code source si vous êtes intéressé par la mise en œuvre réelle.

Le dessous est un échantillon de ce que ce référentiel de base ressemble à:

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

Merci pour la réponse!

Oui, c'est un moyen simple et direct de le résoudre.Mais mon problème est que je veux m'assurer qu'il y a une transaction entourant une opération de référentiel, même si le service d'application, le référentiel etc n'est pas appelé par une requête web (autres types de clients), donc je voulais avoir une transaction le niveau le plus bas (par exemple, session.Save), puis utilisez TransactionScope pour créer une transaction plus longue si nécessaire. Mais votre solution est simple et j'aime ça, mabye je vais l'utiliser et ensuite m'assurer que les autres clients utilisent aussi les transactions.

+0

alors utilisez-vous ce "service" dans le contexte de WCF/ASMX ou est-ce un service de type domaine dans votre application web? –

1

Le cycle de vie des transactions devrait être:

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

Bon exemple. J'ai copié votre code dans ma réponse à "http://stackoverflow.com/a/41255520/5779732". J'ai également mentionné votre nom et lien avec cette réponse. –

1

Vous pouvez réellement vérifier si une transaction est active à l'aide: Session.Transaction.IsActive. Si vous n'êtes pas actif, vous pouvez en créer un. Vous pouvez également créer une méthode Transact qui effectue automatiquement cette opération pour vous. Voici un extrait qui est la plupart du temps de 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; 
          } 
      ); 
    } 
} 
Questions connexes