5

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

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

根据最佳实践,在我们的 Repository 基类中,我们总是围绕 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(我不想编写自己的事务管理器)。为了测试这是否有效,我使用了一个不调用 .Complete() 的外部 TransactionScope 来强制回滚:

存储库保存():

    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); 语句执行了一个带有 SET Text = "TestCahgned" 的 UPDATE 代替触发(它应该在 BEGIN TRAN 和 ROLLBACK TRAN 之间触发)。NHibernate 从 level1 缓存中读取值并触发 UPDATE 到数据库。不是预期的行为!?据我了解,每当在 NHibernate 范围内完成回滚时,您还需要关闭并取消绑定当前会话。

我的问题是:有谁知道使用 TransactionScope 和 ManagedWebSessionContext 的好方法?

4

4 回答 4

2

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

此外,我的基本存储库不会以任何方式进行会话 - 它会询问当前会话,然后对会话执行一些工作。此外,我不会在这个基类中用事务包装任何东西。相反,整个 http 请求是一个工作单元。

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

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

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;
    }
}
于 2009-10-05T18:39:41.387 回答
1

感谢你的回答!

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

于 2009-10-06T10:50:34.100 回答
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();
}
于 2011-05-06T16:04:18.523 回答
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;
                           }
            );
    }
}
于 2011-10-26T23:29:47.707 回答