NHibernate не собирает изменения - PullRequest
       40

NHibernate не собирает изменения

3 голосов
/ 21 сентября 2009

Мне интересно, при каких обстоятельствах может произойти сбой следующего кода 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 всегда не выполняется.

Если я вручную вызываю session.Flush после SaveOrUpdate, тогда запрос на выборку выполняется успешно, однако я подумал, что нам не нужно вручную вызывать flush? Насколько я понимаю, NHibernate должен быть достаточно умен, чтобы понять, что Foo обновлен, поэтому второй запрос на выборку должен быть успешным.

Наблюдая за сгенерированным SQL, кажется, что второй SQL-запрос на выбор выполняется перед первым sql SaveOrUpdate.

На самом деле, если я заверну весь метод в транзакции, он завершится успешно:

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

Теперь sql SaveOrUpdate будет выполняться до sql Linq.Where. Это немного странно, поскольку мне даже не нужно совершать транзакцию между ними.

Что происходит?

Ответы [ 6 ]

3 голосов
/ 21 сентября 2009

Обратите внимание, что вам нужна транзакция, чтобы 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);
}

Обратите внимание, что вы не вызываете Save, Update или SaveOrUpdate, когда хотите сохранить изменения, которые вы внесли в объект, который Session уже отслеживает вниз к базе данных. NHibernate работает иначе, чем другие ORM: если он отслеживает объект, то он выяснит, когда отправить изменения в базу данных, и вам не нужно будет указывать это для этого.

3 голосов
/ 21 сентября 2009

Я рекомендую вам использовать транзакции 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();
  }
}
1 голос
/ 21 сентября 2009

«Мне интересно, при каких обстоятельствах может произойти сбой следующего кода 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 <>. У меня есть много таких тестов, которые регулярно проходят.

0 голосов
/ 21 сентября 2009

Вы должны закрыть сессию и создать новую перед Assert.

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();
  }
}
0 голосов
/ 21 сентября 2009

Если я вызову сеанс вручную. SaveOrUpdate, затем запрос выбора преуспевает.

Для начала: ваш вызов 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);
0 голосов
/ 21 сентября 2009

Возможно, у вас неправильный набор флешмодов. Вам нужно установить режим сброса в сеансе на Авто, чтобы он автоматически сбрасывал сеанс перед каждым запросом, в противном случае вам нужно сбрасывать сеанс вручную, чтобы принудительно сохранить изменения.

...