Страницы

Поиск по вопросам

среда, 5 декабря 2018 г.

Изменение сущностей в событии OnFlushDirty в NHibernate

Есть сущность "настройки", в которой хранится путь к данным. Есть сущности, которые пользуются настройками, чтобы строить свой путь к данным.
Хочется на изменении настроек валидировать пути остальных сущностей и пересохранять их при необходимости. Как это пытаюсь сделать:
Перекрыл Interceptor и ловлю событие OnFlushDirty, которое позволяет отловить изменение нужного свойства:
var folderIndex = propertyNames.ToList().IndexOf(nameof(Folder)); if (folderIndex > -1 && previousState != null) { var previous = previousState[folderIndex] as string; var current = currentState[folderIndex] as string; if (previous != current) { var someEntities = session.Query().Where(m => m.Setting == this).ToList(); foreach (var someEntit in someEntities) { someEntity.RefreshFolder(); } } }
Расчёт был на то, что раз изменения внутри сессии (уже в коммите транзакции), то изменения подцепятся автоматически. Не помогло. Если после someEntity.RefreshFolder(); добавить session.SaveOrUpdate(someEntity) то чуда не происходит и сущности тоже не сохраняются.
Снаружи вызов настроек обёрнут в транзакцию:
using (var tranc = session.OpenTransaction()) { try { session.SaveOrUpdate(setting); tranc.Commit(); } catch (System.Exception) { tranc.Rollback(); throw; } } В SomeEntity есть ссылка на Setting, как видно в п1, и если я попытаюсь сделать отдельную транзакцию, то оно уходит в SO, т.к. для сохранения сущности становится необходима сохраненная настройка, а она снова вызовет сохранение сущности. У всех сессий включен session.FlushMode = FlushMode.Commit, чтобы случайные изменения в сессии (которые например потом были провалидированы исключением) не пытались сохраняться в базу.
В целом, хочется чтобы операция смены папки в настройках была транзакционной, потому что если нет - то можно просто после завершения транзакции делать те же операции не связывая их друг с другом, но тогда гарантировать валидность данных уже нельзя.


Ответ

Судя по всему, после вызова tranc.Commit(); добавлять объекты для коммита уже нельзя. От этого и пляшем.
Основная причина использования OnFlushDirty - трекер изменений в сущности. Эту информацию можно достать и вручную:
var impl = session.GetSessionImplementation(); var key = impl.PersistenceContext.GetEntry(entity);
if (key == null) { var name = impl.GuessEntityName(entity); var persister = impl.GetEntityPersister(name, entity); return new ChangeTrackerArgs(persister.GetPropertyValues(entity, EntityMode.Poco), null, persister.PropertyNames); }
var current = key.Persister.GetPropertyValues(entity, EntityMode.Poco); return new ChangeTrackerArgs(current, key.LoadedState, key.Persister.PropertyNames);
Таким образом, каждый session.SaveOrUpdate(entity) превращается в
var state = GetChangeTrackerArgs(entity) entity.BeforeSave(state); session.SaveOrUpdate(entity);
Т.е. вызов сохранения любой сущности может теперь в BeforeSave сделать аналогичный вызов для любой другой сущности и работать будет для любой вложенности.

Interceptor с его OnFlushDirty всё ещё нужен, т.к. кроме явно вызванных SaveOrUpdate есть ещё неявные изменения, которые поймал хибер. Поэтому, там аналогичный вызов entity.BeforeSave(new ChangeTrackerArgs(currentState, previousState, propertyNames)); Этот вызов уже не сумеет добавить в транзакцию сущности, но зато всё равно провалидирует сущность перед сохранением, если она вдруг попадёт в сохранение неявно.

В дополнение к вышесказанному, хоть это и не касается вопроса. Трекер изменений в хибере работает на сессиях, поэтому для нормальной работы ему нужно получение сущности в конкретной сессии, а потом и её изменение. Если сущность получена в одной сессии, а в другой пытается сохраниться - трекер будет пуст, т.е. все мучения выше - не сильно полезны.

Комментариев нет:

Отправить комментарий