NHibernate、事务和TransactionScope

5

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

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

在我们的存储库基类中,我们始终像最佳实践中所述一样,在我们的SaveOrUpdate、Delete、Get方法周围包装了一个事务:

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

但是,如果您需要在应用程序服务中放置事务以包括多个存储库调用(保存、删除等),则这种方法将不起作用。
因此,我们尝试使用TransactionScope(我不想编写自己的transactionmanager)。为了测试它是否有效,我使用一个外部TransactionScope,它不调用.Complete()来强制回滚:
存储库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,它就会起作用!发生的情况是数据库中根本没有回滚,但是当执行testRepository.GetById(testEntity.Id); 语句时,会触发一个带SET Text =“TestCahgned”的UPDATE(它应该在BEGIN TRAN和ROLLBACK TRAN之间触发)。 NHibernate从级别1缓存中读取值,并向数据库发送UPDATE。这不是预期的行为吗?从我所了解的每当在NHibernate范围内执行回滚时,您还需要关闭和解绑定当前会话。

我的问题是:是否有人知道使用TransactionScope和ManagedWebSessionContext完成此操作的好方法?


1
如果您正在使用TransactionScope,则需要使用NHibernate 2.1。只有在2.1版本中,NHibernate才真正与TransactionScope进行了良好的集成。 - Rohit Agarwal
4个回答

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

2

交易生命周期应该是:

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
好的例子。我已经将你的代码复制到我的答案中,链接为“http://stackoverflow.com/a/41255520/5779732”。我还在那里提到了你的名字和这个答案的链接。 - Amit Joshi

1

谢谢你的回答!

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


你是在使用WCF/ASMX的上下文中使用这个“服务”,还是这是你的Web应用程序内部的域服务? - Toran Billups

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接