2009-09-21 2 views
3

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

В самом деле, если бы я обернуть весь метод в сделке, то она преуспевает:

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

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

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

+0

Я думаю, что ваш вопрос вводит в заблуждение. Я чувствую, что вы хотите знать о NHibernate Unit of work patterns, касающемся синхронизации изменений с БД. Не могли бы вы прояснить? –

+0

Действительно, все, что я хочу, это для успешного теста: я хочу сохранить и перезагрузить Foo надежным, несложным способом. – cbp

ответ

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(); 
    } 
} 
+0

Интересно, что добавление транзакции работает, даже если я не совершаю фиксацию после вызова SaveOrUpdate. Как бы то ни было, добавление транзакции изменяет поведение NHibernate, которое я нахожу немного странным. Тем не менее, я уже использую TransactionScope (поскольку это устаревшее приложение), что означает, что я не могу использовать NHibernate BeginTransaction (по крайней мере, я столкнулся со всеми типами проблем, если попытаюсь использовать его). Однако, с TransactionScope, Assert терпит неудачу. Похоже, мне нужен способ заставить NHibernate вести себя так, как будто я вызвал BeginTransaction, но вместо этого использовал TransactionScope. – cbp

+0

@cbp: Это обескураживает. Будьте осторожны при использовании транзакции без вызова метода Commit. Транзакция будет неявно откатна. Эта статья: http://forum.springframework.net/showthread.php?t=5351, может вам помочь. Он посвящен интеграции TransactionScope и ITransaction. Кстати, используете ли вы инструмент профилирования или проверяете файл журнала sql, созданный движком NHibernate? Если это так, либо запрос сохранения/обновления не происходит, когда вы ожидаете, либо возникли проблемы с механизмом кэширования первого уровня NHibernate. Я подозреваю, что это так. –

+0

@cbp: Я хотел сказать, что если вы смотрите на sql-журнал, вы можете точно определить, когда выполняются инструкции по базе данных. Как вы можете видеть, это не мучительно очевидно, когда это происходит на самом деле. –

0

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

+0

FlushMode - это значение по умолчанию, которое я считаю автоматическим? – cbp

0

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

Для начала: ваш вызов SaveOrUpdate() даже не требуется.

Вот некоторые вещи, которые нужно помнить при использовании сеанса NH:

  • Когда вы Загруженный объект из сессии, сессия будет продолжать отслеживать изменения этого объекта
  • Вызывать Session.update (сущность) говорит только NHibernate сессии, что он должен начать отслеживания объекта, он не идет и записи изменений в БД

Таким образом, в вашем случае, потому что вы загрузили объект из session уже, вызов session.Update() ничего не делает, поскольку он уже отслеживается. Вы можете Infact заставить обновление базы данных, просто выполнив следующие действия:

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

Как я уже сказал, да, я могу вызвать Flush вручную, и утверждение выполнено успешно.Однако кажется, что это не способ использования NHibernate: не обязательно ручно звонить Flush во всем мире - см. Мой вопрос здесь: http://stackoverflow.com/questions/1443214/flushing-in-nhibernate – cbp

+0

Ницца, Стефан в другом посте сказал точно, что я сказал в любом случае. Вы спросили: «Что происходит?» а не «Зачем мне нужно вызвать Flush()?». Возможно, перечитайте http://www.nhforge.org/doc/nh/en/index.html#manipulatingdata-flushing, чтобы собрать свои мысли. –

+0

Хорошо, проблема не столько в промывке. Проблема в том, что NHibernate не поднимает обновленный foo.SomeProperty, поэтому второй запрос session.Linq.Where возвращает 0 записей. – cbp

0

Вы должны закрыть сессию и создать теперь один перед 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

Если я собираюсь сделать все это, не проще ли просто позвонить Флешу? Я думал, что NHibernate должен быть достаточно умным, чтобы понять, что объект обновлен. – cbp

+0

Если вы попробовали флеш и попробовали другой метод флеша .... Я использую этот метод в своих тестах –

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); 
} 

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

1

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

Смежные вопросы