【问题标题】:NHibernate not picking up changesNHibernate 不接受更改
【发布时间】:2010-11-29 23:04:07
【问题描述】:

我想知道在什么情况下下面的 NHibernate 代码会失败:

var session = NHibernateSessionManager.CurrentSession;

var foo = session.Linq<Foo>.ToList()[0];

foo.SomeProperty = "test";

session.SaveOrUpdate(foo);

var reloadedFoos = session.Linq<Foo>
                         .Where(x => x.SomeProperty == "test");

Assert.That(reloadedFoos.Count > 0);

Assert 语句总是失败。

如果我在SaveOrUpdate之后手动调用session.Flush,那么select查询成功,但是我认为我们不必手动调用flush?我的理解是 NHibernate 应该足够聪明地意识到 Foo 已经更新,所以第二个 select 查询应该会成功。

观察生成的 SQL,似乎第二个选择查询的 SQL 在第一个 SaveOrUpdate 的 sql 之前执行。

实际上,如果我将整个方法包装在一个事务中,那么它就成功了:

using(NHibernateSessionManager.CurrentSession.BeginTransaction()
{
    // Same code as above
}

现在 SaveOrUpdate 的 sql 将在 Linq.Where sql 之前执行。这有点奇怪,因为我什至不必在两者之间提交事务。

发生了什么事?

【问题讨论】:

  • 我认为您的问题具有误导性。我觉得您想了解 NHibernate 工作单元模式,关于与数据库同步更改。你介意澄清一下吗?
  • 我真正想要的是让上述测试成功:我希望能够以可靠、简单的方式保存和重新加载 Foo。

标签: c# .net nhibernate flush


【解决方案1】:

我建议您利用 NHibernate 事务。如果不使用它们,NHibernate 完全有可能无法确定何时发出您的 SaveOrUpdate 调用。

您会发现,即使是只读语句,在使用事务时也会表现得更好。详情请见http://nhprof.com/Learn/Alert?name=DoNotUseImplicitTransactions

例如:

using(var session = NHibernateSessionManager.CurrentSession)
{
  using(var transaction = session.BeginTransaction())
  {
    var foo = session.Linq<Foo>.ToList()[0];

    foo.SomeProperty = "test";

    session.SaveOrUpdate(foo);
    transaction.Commit();
  }
}

【讨论】:

  • 有趣的是,添加事务有效,即使我在调用 SaveOrUpdate 后没有提交。不知何故,添加事务会改变 NHibernate 的行为方式,我觉得这有点奇怪。但是,我已经在使用 TransactionScope(因为这是一个遗留应用程序),这意味着我不能使用 NHibernate BeginTransaction(至少,如果我尝试使用它,我会遇到所有类型的问题)。但是,对于 TransactionScope,Assert 会失败。似乎我需要一种方法让 NHibernate 的行为就像我调用了 BeginTransaction 一样,而是使用 TransactionScope。
  • @cbp:这令人不安。小心使用事务而不调用其 Commit 方法。事务将被隐式回滚。这篇文章:forum.springframework.net/showthread.php?t=5351,可能对你有所帮助。它处理集成 TransactionScope 和 ITransaction。顺便说一句,您是否使用分析工具或检查 NHibernate 引擎创建的 sql 日志文件?如果是这样,要么保存/更新查询没有按照您的预期发生,要么NHibernate 的第一级缓存机制存在问题。我怀疑是前者。
  • @cbp:我的意思是说,如果您正在查看 sql 日志,您可以准确地确定何时对数据库执行语句。正如您所看到的,当这种情况实际发生时并不是很明显。
  • @David,是的,我正在使用 ShowSql 配置方法查看生成的 SQL。当我的所有代码都包含在 NHibernate 事务中时,SaveOrUpdate 方法会导致 Insert 方法在选择查询之前执行。当代码未包装在事务中时,选择查询的 sql 在 Insert 语句有机会提交之前执行。
  • @cbp:那么毫无疑问,问题在于 TransactionScope 和 ITransaction 一起工作。好吧,这另一个帖子stackoverflow.com/questions/1279926/… 与您的一级缓存有类似的问题。很抱歉让你读了这么多,但老实说我没有答案。
【解决方案2】:

请注意,您需要 NHibernate 的事务才能“智能”。

这是它的工作原理:

var session = NHibernateSessionManager.CurrentSession;
using(NHibernateSessionManager.CurrentSession.BeginTransaction()) {
    var foo = session.Linq<Foo>.ToList()[0];
    foo.SomeProperty = "test";
    var reloadedFoos = session.Linq<Foo>()
        .Where(x => x.SomeProperty == "test");
    Assert.That(reloadedFoos.Count > 0);
}

还请注意,当您想要保存对Session 已经跟踪的对象所做的更改时,您确实调用SaveUpdateSaveOrUpdate回到数据库。 NHibernate 与其他 ORM 的工作方式不同:如果它正在跟踪一个对象,那么它会确定何时将更改发送到数据库,而您无需告诉它这样做。

【讨论】:

    【解决方案3】:

    “我想知道在什么情况下以下 NHibernate 代码可能会失败:”我认为您已经为您自己的问题提供了至少一个答案:当代码在隐式事务中运行时。请参阅this post from Ayende,其中提到了隐式事务中的不一致行为。我有许多类似于您的代码的单元测试,除了测试驱动程序提供了一个包装事务,例如,

    [Test]
    public void Can_Update_Account() {
            Account account = PersistenceContext.Get<Account>(TEST_ACCOUNT_ID);
    
            string accountNumber = RandomString(10);
            account.AccountNumber = accountNumber;
    
            Account account1 = PersistenceContext.GetAll<Account>().Where(x => x.AccountNumber == accountNumber).SingleOrDefault();
            Account account2 = PersistenceContext.Get<Account>(account.Id);
            Assert.AreEqual(account.Id, account1.Id);
            Assert.AreEqual(accountNumber, account2.AccountNumber);
        }
    

    [GetAll() 是 Linq 的一个精简包装器。] 我有很多这样的测试定期通过。

    【讨论】:

      【解决方案4】:

      您可能设置了不正确的刷新模式。您需要将会话上的刷新模式设置为自动,以便在每次查询之前自动刷新会话,否则您需要手动刷新会话以强制保存更改。

      【讨论】:

      • FlushMode 是默认的,我相信是 Auto?
      【解决方案5】:

      您必须关闭会话并在 Assert 之前创建一个 now。

      using(var session = NHibernateSessionManager.CurrentSession)
      {
        using(var tx = session.BeginTransaction())
        {
          var foo = session.Linq<Foo>.ToList()[0];
          foo.SomeProperty = "test";
          session.SaveOrUpdate(foo);  
          tx.Commit();
        }
      }
      
      //create a new session here, the code depend if you use RhinoCommons (like me), no Rhino
      
      using(var session = NHibernateSessionManager.CurrentSession)
      {
        using(var tx = session.BeginTransaction())
        {
          var reloadedFoos = session.Linq<Foo>
                  .Where(x => x.SomeProperty == "test");
          Assert.That(reloadedFoos.Count > 0);
          tx.Commit();
        }
      }
      

      【讨论】:

      • 如果我要做这一切,只是调用Flush不是更容易吗?我认为 NHibernate 应该足够聪明,可以确定实体已更新。
      • 如果你尝试了flush并尝试其他flush方法......我在我的测试中使用这个方法
      【解决方案6】:

      如果我手动调用 session.Flush 之后 SaveOrUpdate,然后选择查询 成功。

      首先:您甚至不需要调用 SaveOrUpdate()。

      以下是使用 NH 会话时要记住的一些事项:

      • 从会话中加载对象后,会话将继续跟踪对该对象的更改
      • 调用 session.Update(entity) 只告诉 NHibernate 会话它应该开始跟踪对象,它不会将更改写入 db

      因此,在您的情况下,因为您已经从会话中加载了一个对象,所以调用 session.Update() 什么都不做,因为它已经被跟踪了。您实际上可以通过执行以下操作来强制更新数据库:

      var session = NHibernateSessionManager.CurrentSession;
      var foo = session.Linq<Foo>.ToList()[0];
      foo.SomeProperty = "test";
      
      session.Flush();
      
      var reloadedFoos = session.Linq<Foo>
                               .Where(x => x.SomeProperty == "test");
      Assert.That(reloadedFoos.Count > 0);
      

      【讨论】:

      • 就像我说的,是的,我可以手动调用 Flush 并且语句成功。但是,这似乎不是使用 NHibernate 的方式:不必在任何地方手动调用 Flush - 请在此处查看我的问题:stackoverflow.com/questions/1443214/flushing-in-nhibernate
      • 很好,Stefan 在另一篇文章中所说的正是我所说的。你问“怎么回事?”不是“为什么我需要调用 Flush()?”。也许重新阅读nhforge.org/doc/nh/en/index.html#manipulatingdata-flushing 以收集您的想法。
      • 好的,问题不在于冲洗。问题是 NHibernate 没有获取更新的 foo.SomeProperty,所​​以第二个 session.Linq.Where 查询返回 0 条记录。
      猜你喜欢
      • 2016-04-21
      • 1970-01-01
      • 2020-05-21
      • 2018-09-16
      • 2021-12-01
      • 1970-01-01
      • 2012-06-15
      • 2014-11-26
      • 1970-01-01
      相关资源
      最近更新 更多