2009-10-05 14 views
5

Estoy tratando de encontrar la mejor solución para manejar transacciones en una aplicación web que utiliza NHibernate.NHibernate, transacciones y TransactionScope

Usamos un IHttpModule y en HttpApplication.BeginRequest abrimos una nueva sesión y la vinculamos a HttpContext con ManagedWebSessionContext.Bind (context, session); Cerramos y desenlazamos la sesión en HttpApplication.EndRequest.

En nuestra clase base del repositorio, siempre envuelto en una transacción nuestra saveOrUpdate, Borrar, Get métodos como, según best practice:

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

Pero entonces esto no funciona, si es necesario poner un transacción en algún lugar de, por ejemplo un servicio de aplicación para incluir varias llamadas al repositorio para guardar, eliminar, etc.

Entonces, lo que probamos es usar TransactionScope (no quería escribir mi propio gestor de transacciones). Para probar que esto funcionó, utilizo una TransactionScope exterior que no llama integro() para forzar una inversión:

Repositorio Save():

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

El bloque que utiliza el repositorio :

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

Esto no funciona. Pero para mí, si NHibernate es compatible con TransactionScope, ¡lo haría! Lo que ocurre es que no hay ningún ROLLBACK en absoluto en la base de datos, sino cuando testRepository.GetById (testEntity.Id); la instrucción se ejecuta una ACTUALIZACIÓN con SET Text = "TestCahgned" se dispara en su lugar (debería haberse disparado entre BEGIN TRAN y ROLLBACK TRAN). NHibernate lee el valor de la memoria caché de nivel 1 y activa una ACTUALIZACIÓN en la base de datos. No se espera un comportamiento? Por lo que entiendo cada vez que se realiza una reversión en el ámbito de NHibernate, también debe cerrar y desvincular la sesión actual.

Mi pregunta es: ¿Alguien sabe de una buena manera de hacer esto usando TransactionScope y ManagedWebSessionContext?

+1

Si está utilizando TransactionScope, es necesario utilizar NHibernate 2.1. Es solo con 2.1 que NH realmente obtuvo una buena integración con TransactionScope. –

Respuesta

2

Tomé un enfoque muy similar. En el HttpModule pregunto en sessionfactory por una nueva sesión + la enlace cuando llega una nueva solicitud. Pero también comienzo la transacción aquí. Luego, cuando la solicitud finaliza, simplemente lo desvinculamos e intento comprometer la transacción.

También mi repositorio base no toma una sesión de ninguna manera, sino que solicita la sesión actual y luego realiza algún trabajo con la sesión. Además, no envuelvo nada dentro de esta clase base con una transacción. En cambio, toda la solicitud http es una sola unidad de trabajo.

Esto puede no ser apropiado para el proyecto en el que está trabajando, pero prefiero este enfoque porque cada solicitud fallará o tendrá éxito como una única unidad atómica. Tengo una publicación completa en el blog here con código fuente, si está interesado en la implementación real.

A continuación es una muestra de lo que este repositorio de base se parece a:

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

Gracias por la respuesta!

Sí, es una manera simple y directa de resolverlo.Pero mi problema es que quiero asegurarme de que haya una transacción alrededor de una operación de repositorio, incluso si el servicio de aplicación, repositorio, etc. no es llamado por una solicitud web (otros tipos de clientes), por lo tanto, quería tener una transacción alrededor el nivel más bajo (por ejemplo, session.Save) y luego usa TransactionScope para crear una transacción más larga si es necesario. Pero su solución es simple y me gusta, mabye lo usaré y luego me aseguraré de que otros clientes también usen las transacciones.

+0

, ¿está utilizando este "servicio" en el contexto de WCF/ASMX o se trata de un dominio como el servicio dentro de su aplicación web? –

1

El ciclo de vida de la transacción debe ser:

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

Buen ejemplo. He copiado tu código en mi respuesta en "http://stackoverflow.com/a/41255520/5779732". También mencioné tu nombre allí y un enlace a esta respuesta. –

1

En realidad se puede comprobar para ver si una transacción está activo usando: Session.Transaction.IsActive. Si uno no está activo, puede crear uno. También puede crear un método Transact que hace la mayor parte de esto automáticamente. He aquí un extracto que es en su mayoría 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; 
          } 
      ); 
    } 
} 
Cuestiones relacionadas