persister.SetPropertyValue in IPreUpdateEventListener doesn't propagate changes into DB on inherited classes

119 views
Skip to first unread message

Filip Kinsky

unread,
Feb 16, 2011, 10:56:57 AM2/16/11
to nhu...@googlegroups.com
We're logging modification date in few of our persisted classes using simple interface and basic IPreInsert/UpdateEventListener implementation which uses this code to set LastModified field:

var trackable = entity as ITrackModificationDate;
if (trackable != null)
{
trackable.LastModified = DateTime.Now;
persister.SetPropertyValue(state, "LastModified", trackable.LastModified);
}

This works for basic classes without any inheritance hierarchy, but when I map inherited classes (LastModified field implemented by the hierarchy root) and update some property which exists just in the inherited class, the listener is triggered, but the modified LastModified property value is not being persisted into database. I isolated the problem and implemented attached failing test, but I'm not able to figure out what should I do in the listener to force NH to update the DB properly. Did anyone face problem like this before?

test classes:

public class Thing : ITrackModificationDate
{
public virtual long Id { get; set; }
public virtual DateTime LastModified { get; set; }
}

public class InheritedThing: Thing
{
public virtual string SomeText { get; set; }
}

public class ThingMap : ClassMap<Thing>
{
public ThingMap()
{
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.LastModified);
}
}

public class InheritedThingMap: SubclassMap<InheritedThing>
{
public InheritedThingMap()
{
Map(x => x.SomeText);
}
}

failing test:

[Fact]
public void InheritedThing_LastModified_Should_BeSetOnUpdate()
{
var t = new InheritedThing {Id = 1, SomeText = "aa"};
session.Save(t);
session.Flush();
session.Clear();

Thread.Sleep(1000);

t = session.Get<InheritedThing>(1L);
t.SomeText = "bb";
session.Update(t);

session.Flush();
session.Clear();

t = session.Get<InheritedThing>(1L);

Assert.True(DateTime.Now.Subtract(t.LastModified).TotalSeconds < 1); //this fails - LastModified property isn't updated in DB
}

Filip Kinsky

unread,
Feb 16, 2011, 10:58:09 AM2/16/11
to nhu...@googlegroups.com
NH 3.0.0.1003

Filip Kinsky

unread,
Feb 17, 2011, 3:11:24 AM2/17/11
to nhu...@googlegroups.com
I just figured out that if I hook SaveEventListeners+UpdateEventListeners+SaveOrUpdateEventListeners instead of PreInsertEventListeners+PreUpdateEventListeners than it starts to work - the test passes. So I wonder why everyone suggests to use the pre-insert/update events (eg. http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx)? Or is this just some bug in NH 3.0.0.1003 pre-insert/update events implementation and the test case in my first post should work?

Fabio Maulo

unread,
Feb 18, 2011, 9:02:33 AM2/18/11
to nhu...@googlegroups.com
remove this
persister.SetPropertyValue(state, "LastModified", trackable.LastModified);

On Thu, Feb 17, 2011 at 5:11 AM, Filip Kinsky <fi...@filovo.net> wrote:
I just figured out that if I hook SaveEventListeners+UpdateEventListeners+SaveOrUpdateEventListeners instead of PreInsertEventListeners+PreUpdateEventListeners than it starts to work - the test passes. So I wonder why everyone suggests to use the pre-insert/update events (eg. http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventlistener-amp-ipreinserteventlistener.aspx)? Or is this just some bug in NH 3.0.0.1003 pre-insert/update events implementation and the test case in my first post should work?

--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.



--
Fabio Maulo

Filip Kinský

unread,
Feb 21, 2011, 2:32:58 AM2/21/11
to nhu...@googlegroups.com
Hello Fabio, thanks for your response. I just tried to remove the row you mentioned, but it didn't help - the test still fails..

Ricardo Peres

unread,
Feb 21, 2011, 9:27:20 AM2/21/11
to nhusers
Filip,

This works:

public class SaveListener: DefaultSaveEventListener
{
protected override Object PerformSaveOrUpdate(SaveOrUpdateEvent
@event)
{
if (@event.Entity is Post)
{
Post post = (@event.Entity as Post);

if (post.ID == 0)
{
//new entity
}

post.Timestamp = DateTime.Now;
}

return base.PerformSaveOrUpdate(@event);
}
}



On Feb 21, 7:32 am, Filip Kinský <fi...@filovo.net> wrote:
> Hello Fabio, thanks for your response. I just tried to remove the row you
> mentioned, but it didn't help - the test still fails..
>
>
>
>
>
>
>
> On Fri, Feb 18, 2011 at 3:02 PM, Fabio Maulo <fabioma...@gmail.com> wrote:
> > remove this
> > persister.SetPropertyValue(state, "LastModified", trackable.LastModified);
>
> > On Thu, Feb 17, 2011 at 5:11 AM, Filip Kinsky <fi...@filovo.net> wrote:
>
> >> I just figured out that if I
> >> hook SaveEventListeners+UpdateEventListeners+SaveOrUpdateEventListeners
> >> instead of PreInsertEventListeners+PreUpdateEventListeners than it starts to
> >> work - the test passes. So I wonder why everyone suggests to use the
> >> pre-insert/update events (eg.
> >>http://ayende.com/Blog/archive/2009/04/29/nhibernate-ipreupdateeventl...

Filip Kinský

unread,
Feb 21, 2011, 10:09:40 AM2/21/11
to nhu...@googlegroups.com
Yes, this works, but this kind of listener is fired before NH performs dirty-checks etc contrary to IPreInsert/Update events. So it's being fired also for entities which are not dirty and there won't be any insert/update SQL executed for them in standard conditions. It can cause needless TimeStamp changes like in this case:

var p = session.Load<Post>(123);
ModifyPostAccordingToUserInput(p); //ch changes here
session.SaveOrUpdate(p); // => p.TimeStamp modified as SQL update executed even though the Post didn't change in fact

 I use simmilar code now as a workaround for my previously mentioned problems, but I added a dirty check so it looks like this:


public class SetModificationDateListener: DefaultSaveOrUpdateEventListener
{
...
protected override object PerformSave(object entity, object id, IEntityPersister persister, bool useIdentityColumn, object anything, IEventSource source, bool requiresImmediateIdAccess)
{
SetModificationDateIfPossible(entity);
return base.PerformSave(entity, id, persister, useIdentityColumn, anything, source, requiresImmediateIdAccess);
}

protected override void PerformUpdate(SaveOrUpdateEvent @event, object entity, IEntityPersister persister)
{
if (@event.Session.IsDirtyEntity(@event.Entity))
{
SetModificationDateIfPossible(entity);
}
base.PerformUpdate(@event, entity, persister);
}

protected override object PerformSaveOrUpdate(SaveOrUpdateEvent @event)
{
if (@event.Session.IsNewEntity(@event.Entity) || @event.Session.IsDirtyEntity(@event.Entity))
{
SetModificationDateIfPossible(@event.Entity);
}
return base.PerformSaveOrUpdate(@event);
}
...
}

I'd still better like to use the IPre-event listeners...

Filip Kinsky

unread,
Feb 21, 2011, 10:13:22 AM2/21/11
to nhu...@googlegroups.com
sorry for the typo..

var p = session.Load<Post>(123);
ModifyPostAccordingToUserInput(p); //*NO* changes here
Reply all
Reply to author
Forward
0 new messages