NHibernate,事务和TransactionScope

时间:2009-10-05 08:37:38

标签: nhibernate session transactions transactionscope

我正在尝试找到在使用NHibernate的Web应用程序中处理事务的最佳解决方案。

我们使用IHttpModule,在HttpApplication.BeginRequest中我们打开一个新会话,然后用ManagedWebSessionContext.Bind(context,session)将它绑定到HttpContext;我们在HttpApplication.EndRequest上关闭和取消绑定会话。

在我们的Repository基类中,根据best practice,我们总是围绕我们的SaveOrUpdate,Delete,Get方法包装一个事务:

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

但是如果你需要在某个地方进行交易,那么这不起作用。一个应用程序服务,包括对Save,Delete等的多个存储库调用。

所以我们尝试的是使用TransactionScope(我不想编写自己的事务管理器)。为了测试这是否有效,我使用外部TransactionScope,它不会调用.Complete()来强制回滚:

Repository Save():

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

使用存储库的块:

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

这不起作用。但对我来说,如果NHibernate支持TransactionScope,它会!发生的事情是数据库中根本没有ROLLBACK但是当testRepository.GetById(testEntity.Id);语句执行UPDATE与SET Text =“TestCahgned”被触发(它应该在BEGIN TRAN和ROLLBACK TRAN之间被触发)。 NHibernate从level1缓存读取值并向数据库发出UPDATE。不期望的行为!?根据我的理解,只要在NHibernate的范围内完成回滚,您还需要关闭和取消绑定当前会话。

我的问题是:有没有人知道使用TransactionScope和ManagedWebSessionContext做到这一点的好方法?

4 个答案:

答案 0 :(得分:2)

我采取了非常类似的方法。在HttpModule中,我向sessionfactory请求新会话+在新请求进入时绑定它。但我也在这里开始事务。然后,当请求结束时,我只是解除绑定并尝试提交事务。

此外,我的基本存储库不会以任何方式进行会话 - 而是会要求当前会话,然后对会话执行一些工作。此外,我不会使用事务将任何内容包装在此基类中。相反,整个http请求只是一个工作单元。

这可能不适合您正在处理的项目,但我更喜欢这种方法,因为每个请求都会失败或成功作为单个原子单元。如果您对实际实施感兴趣,我有完整的博客文章here及源代码。

以下是此基本存储库的示例:

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 :(得分:1)

感谢您的回答!

是的,这是解决问题的一种简单而直接的方法。但我的问题是我想确保存在一个围绕存储库操作的事务,即使Web请求(其他类型的客户端)没有调用应用程序服务,存储库等,因此我希望周围有一个事务最低级别(例如session.Save)然后使用TransactionScope在需要时创建更长的事务。但是你的解决方案很简单,我喜欢它,mabye我将使用它,然后确保其他客户端也使用事务。

答案 2 :(得分:1)

交易生命周期应为:

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();
}

答案 3 :(得分:1)

您实际上可以使用Session.Transaction.IsActive检查交易是否处于活动状态。如果一个未激活,您可以创建一个。您还可以创建一个Transact方法,自动为您完成大部分操作。这是一段摘录,主要来自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;
                           }
            );
    }
}