3

我想知道在什么情况下以下 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 已经更新,所以第二个选择查询应该成功。

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

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

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

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

到底是怎么回事?

4

6 回答 6

3

我建议您利用 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();
  }
}
于 2009-09-21T00:49:37.303 回答
3

请注意,您需要 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);
}

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

于 2009-09-21T12:16:58.607 回答
1

“我想知道在什么情况下以下 NHibernate 代码可能会失败:”我认为您已经为您自己的问题提供了至少一个答案:当代码在隐式事务中运行时。请参阅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<> 的一个薄包装器。] 我有很多这样的测试定期通过。

于 2009-09-21T16:56:56.730 回答
0

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

于 2009-09-21T00:50:05.790 回答
0

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

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

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

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

因此,在您的情况下,因为您已经从会话中加载了一个对象,所以调用 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);
于 2009-09-21T01:20:31.813 回答
0

您必须关闭会话并在 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();
  }
}
于 2009-09-21T03:39:50.490 回答