NHibernate FlushMode Auto не очищается перед поиском
Хорошо, я видел несколько постов, в которых говорилось почти об одном и том же, но точки были немного другими.
Это классический случай: я сохраняю / обновляю сущность и в рамках ЖЕ СЕССИИ пытаюсь получить их из базы данных (используя критерии / поиск / перечисление / и т. Д.) С помощью FlushMode = Auto. Дело в том, что NHibernate не сбрасывает обновления перед запросом, поэтому я получаю противоречивые данные из базы данных.
"Достаточно справедливо", некоторые люди скажут, как говорится в документации:
Этот процесс flush происходит по умолчанию в следующих точках:
- из некоторых вызовов Find() или Enumerable()
- от NHibernate.ITransaction.Commit()
- от ISession.Flush()
Смелые "некоторые призывы" ясно говорят о том, что NH не несет никакой ответственности вообще. ИМО, однако, у нас здесь есть проблема согласованности, потому что документ также утверждает, что:
За исключением случаев, когда вы расшифруете Flush(), нет абсолютно никаких гарантий относительно того, когда Session выполняет вызовы ADO.NET, только порядок, в котором они выполняются. Однако NHibernate гарантирует, что методы ISession.Find(..) никогда не будут возвращать устаревшие данные; и при этом они не будут возвращать неправильные данные.
Итак, если я использую CreateQuery (Найти замену) и фильтрую для сущностей со свойством Value = 20, NH не сможет вернуть сущности со значением = 30, верно? Но это то, что происходит на самом деле, потому что сброс не происходит автоматически, когда это должно произойти.
public void FlushModeAutoTest()
{
ISession session = _sessionFactory.OpenSession();
session.FlushMode = FlushMode.Auto;
MappedEntity entity = new MappedEntity() { Name = "Entity", Value = 20 };
session.Save(entity);
entity.Value = 30;
session.SaveOrUpdate(entity);
// RETURNS ONE ENTITY, WHEN SHOULD RETURN ZERO
var list = session.CreateQuery("from MappedEntity where Value = 20").List<MappedEntity>();
session.Flush();
session.Close();
}
В конце концов: я ошибаюсь, это ошибка или просто непредсказуемая функция, поэтому каждый должен вызвать Flush, чтобы убедиться в ее работе?
Спасибо.
Филипе
3 ответа
Я не очень знаком с исходным кодом NHibernate, но этот метод из реализации ISession в версии 2.1.2.GA может ответить на вопрос:
/// <summary>
/// detect in-memory changes, determine if the changes are to tables
/// named in the query and, if so, complete execution the flush
/// </summary>
/// <param name="querySpaces"></param>
/// <returns></returns>
private bool AutoFlushIfRequired(ISet<string> querySpaces)
{
using (new SessionIdLoggingContext(SessionId))
{
CheckAndUpdateSessionStatus();
if (!TransactionInProgress)
{
// do not auto-flush while outside a transaction
return false;
}
AutoFlushEvent autoFlushEvent = new AutoFlushEvent(querySpaces, this);
IAutoFlushEventListener[] autoFlushEventListener = listeners.AutoFlushEventListeners;
for (int i = 0; i < autoFlushEventListener.Length; i++)
{
autoFlushEventListener[i].OnAutoFlush(autoFlushEvent);
}
return autoFlushEvent.FlushRequired;
}
}
Я полагаю, это означает, что автоматическая очистка гарантирует только последовательность внутри транзакции, что имеет некоторый смысл. Попробуйте переписать свой тест с использованием транзакции, мне очень интересно, решит ли это проблему.
Если вы думаете об этом, запрос в вашем примере всегда должен идти в БД. Сессия не является полным кэшем всех записей в БД. Таким образом, на диске могут быть другие объекты со значением 20. А так как вы не зафиксировали () транзакцию или flush(), сессия NH не может узнать, к какому "представлению" вы хотите обратиться (DB | Session).
Кажется, что "Лучшая практика" - делать все (получать и устанавливать) внутри явных транзакций:
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction())
{
// execute code that uses the session
tx.Commit();
}
Смотрите здесь для получения подробной информации.
Управление и настройка hibernate - это форма искусства.
почему вы устанавливаете начальное значение 20, сохраняете, а затем изменяете его на 30?
На практике, если вы собираетесь изменить сеанс, а затем запросить сеанс, вы можете явно выполнить сброс между этими операциями. У вас может быть небольшое снижение производительности (в конце концов, вы не позволите hibernate оптимизировать очистку сеанса), но вы можете вернуться к нему, если это станет проблемой.
Вы цитировали, что "методы session.find никогда не будут возвращать устаревшие данные". Я бы изменил ваш код, чтобы использовать find вместо createQuery, чтобы посмотреть, работает ли он.