Swojego czasu Ayende na blogu opisał dwa interfejsy, który umożliwiają w NH zrobienie prostego audytu. Interfejsy (IPreUpdateEventListener i IPreInsertEventListener) umożliwiają wstrzyknięcie pewnej operacji tuż przed wstawieniem jak i aktualizacją obiektu – idealne miejsce dla wstawienia daty aktualizacji i modyfikacji.

Niestety, działają one jedynie dla encji, które nie posiadają dziedziczenia, które jest mapowane na tabelę. Dla przykładu mamy encję bazową EntityBase po której dziedziczy encja Organisation implementująca interfejs IAuditable. EntityBase nie jest mapowane na tabelę, jedynie Organisation. W takim wypadku, interfejsy zadziałają bezproblemowo.

Organisation

Jeżeli zaś mamy sytuację EntityBase po której dziedziczy Licence implementująca IAuditable (mapowana na tabelę, joined-subclass) i następnie po Licence dziedziczy konkretna licencja na przykład IppcLicence (również mapowana), interfejsy zadziałają pięknie dla wstawienia obiektu IppcLicence, zaś przy aktualizacji mimo wykonania kodu, pole data modyfikacji jak i użytkownik wykonujący daną operację nie zostaną zapisane do bazy danych.

IppcLicence

Jest to spowodowane przez dziedziczenie. Gdyby Licence nie była mapowana na tabelę, kod by działał poprawnie. Niestety, jeżeli gdzieś w modelu będziemy chcieli się odwołać do Licence to trzeba ją z mapować. Na przykład organizacja może mieć przypisaną listę licencji, nie interesuje nas typ ale lista. W tym momencie niezbędne jest mapowanie encji Licence – choć muszę powiedzieć, że nie próbowałem z union-subclass ze względu na to, że nie działa to porwanie z mapowaniem AutoMapping od FluntNHibernate.

Na ratunek przychodzi klasa DefaultSaveOrUpdateEventListener, po której można podziedziczyć i przeciążyć w niej metodę PerformSaveOrUpdate. Osobiście odradzam przeciążania klasy DefaultFlushEntityEventListener chyba, że chce nam się bawić w rozpoznawanie czy coś zostało zmienione, w przeciwnym wypadku za każdym razem kiedy sesja jest flush zostanie wywołany event OnFlushEntity, który generalnie psuje całkowicie audyt – wystarczy zrobić query i mamy aktualizację pól.

Wracając do DefaultSaveOrUpdateEventListener, mimo tego iż działa on w większości przypadków, to jednak ma on problem z metodą Session.Save na naszej IppcLicence. Dokładnie mówiąc przy save na tak skonturowanym modelu, zdarzenie PerformSaveOrUpdate nie zostanie w ogóle wywołane. Zostanie ono zaś wywołane dla przykładu z Organisation.

Rozwiązaniem problemu jest wywołanie metody Session.SaveOrUpdate, która spowoduje wywołanie zdarzenia PerformSaveOrUpdate dla IppcLicence.

Dziwne to jak nie wiem co, możliwe, że bug – nie wiem. Wiem tylko to, że teraz muszę pamiętać gdy jak zapisuje licencje to muszę robić SaveOrUpdate.

Temat został również poruszony tutaj wraz z kodem przykładowym reprezentującym dane problemy – oprócz problemu z Session.Save.

Może ktoś z was ma na to rozwiązanie lub zetknął się z tym problemem i jakoś rozsądnie go rozwiązał?