【问题标题】:NHibernate - use Session.Flush() in Dispose() method - good idea or not?NHibernate - 在 Dispose() 方法中使用 Session.Flush() - 好主意与否?
【发布时间】:2011-03-11 15:49:13
【问题描述】:

我通过一组接口在我的应用程序中使用 NHibernate。 ISession 封装在一个名为 IUnitOfWork 的接口中,其具体实现如下所示:

public class UnitOfWork : IUnitOfWork
{
  private ISession _session;

  public UnitOfWork()
  {
    _session = KctcSessionFactory.OpenSession();
  }

  public void Dispose()
  {
    try
    {
      _session.BeginTransaction();
      _session.Flush();
      _session.Transaction.Commit();
    }
    catch (Exception)
    {
      _session.Transaction.Rollback();
      _session.Transaction.Dispose();
      throw;
    }
  }

  public T Load<T>(int id)
  {
    return _session.Load<T>(id);
  }

  public IQueryable<T> GetList<T>()
  {
    return _session.Linq<T>();
  }

  public void Save(object entity)
  {
    _session.SaveOrUpdate(entity);
  }

  public void Delete(object entity)
  {
    _session.Delete(entity);
  }
}

IUnitOfWork 由温莎城堡经营,生活方式为PerWebRequest。这意味着从 Web 请求的角度来看,IUnitOfWork 实际上是一个单例,并在请求结束时自动释放。

这段代码在我看来完全是万无一失的。当IUnitOfWork 在 Web 请求结束时被释放时,所有保存的实体都将被刷新,并且整个操作被包装在一个事务中,因此它是原子完成的。我不应该在这个类之外使用事务。我对吗?这段代码安全吗?还是我错过了什么可怕的事情?

为清楚起见进行了编辑:我想知道我是否正确假设所有刷新/提交到数据库将等到会话被释放,因此可以包装在单个事务中Dispose 方法。或者是否存在在我在 Dispose 方法中明确执行数据之前可能会刷新数据的情况?或者我可能出于其他原因需要显式使用事务?

【问题讨论】:

  • 哎呀!我刚刚意识到当我可以提交事务时我不需要 _session.Flush() 。顺便说一句,这不是生产代码。

标签: nhibernate transactions castle-windsor unit-of-work


【解决方案1】:

提交事务会自动刷新会话,所以不需要。我不会将 Load 和 GetList 方法放在一个工作单元中,因为这更关心存储库或 DAO。否则我觉得很好。

【讨论】:

  • 谢谢。回复:加载和获取列表。我所有的数据访问都是通过存储库完成的,但是存储库又调用 IUnitOfWork 的方法。这是因为在 NHibernate 中,此功能是通过 ISession 访问的,正如您在代码中看到的那样。
  • @David,是的,我了解您的设计。我要说的是它不是最优的。我会遵循关注点分离的原则。工作单元应负责启动事务,并在完成时提交和处置会话。您的存储库应该关注 CRUD。没有什么能阻止你注入 NHibernate Session 并将它们用于不同的事情。您只需要确保将您的 IOC 容器配置为使用相同的 Session 实例注入它们。
  • 哦,我明白了,谢谢你的澄清。关于工作单元的职责应该是什么,我想您的想法比我的想法要清晰得多。我对工作单元所做的工作的概念几乎来自于 ISession 本身就是一个工作单元实现的想法。我从未想过 ISession 本身有更广泛的责任。
  • @David,我没有注意到您实际上只是为了刷新而开始交易。 @mookid8000 对工作单元有很好的实现。一个工作单元管理你的会话,而存储库进行 crud 交互。您无需将 crud 交互包装在另一层中。
【解决方案2】:

我发现您使用 NHibernate 的方式存在一些问题。

1) 如果您使用默认设置的 NHibernate,它会在刷新模式 AUTO 下运行,这意味着有时会在执行查询时将更改刷新到数据库以避免过时的结果。这可能意味着这些东西会在您的事务之外过早刷新。

2) 当您的事务仅跨越刷新操作时,读取发生在事务之外,并且不受与事务相同的隔离级别的约束。要使事务真正原子和隔离,您需要在创建会话后立即启动事务。

更好的 uow 应该是这样的:

public class UnitOfWork : IDisposable
{
    ISession currentSession;
    bool shouldCommit;

    public UnitOfWork()
    {
        currentSession = KctcSessionFactory.OpenSession();
        currentSession.BeginTransaction();
    }

    public void Commit()
    {
        shouldCommit = true;
    }

    public void Dispose()
    {
        if (shouldCommit)
        {
            currentSession.Transaction.Commit();
        }
        else
        {
            currentSession.Transaction.Rollback();
        }

        currentSession.Dispose();
    }
}

允许有人需要以某种方式检查 Web 请求是否成功且无异常的使用场景,如果是这种情况,则在当前 uow 上调用 Commit

这有意义吗?

【讨论】:

  • 谢谢。这是一个很大的帮助。确保异常防止提交 UOW 应该不是特别困难。 IOC 容器以及 UOW 对任何异常处理代码都是可见的。
  • 但是您已经提醒我,实际上可能不希望这么晚才将 ISession 刷新到数据库。例如,我可能需要在执行其他操作之前知道刷新是否成功,例如发送确认电子邮件。我有很多事情要考虑。
  • 一般来说,我会建议在某种消息传递基础架构之后将与外部系统的集成(例如发送邮件)解耦。如果您为此使用 MSMQ,例如通过使用 NServiceBus,您可以让消息传递和数据库操作参与同一个事务,从而允许 SendMail 消息传递事务与数据库事务一起回滚。
猜你喜欢
  • 1970-01-01
  • 2011-07-24
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 2011-10-26
  • 2014-02-13
  • 1970-01-01
相关资源
最近更新 更多