views:

234

answers:

3

I'm implementing a custom EventListener to save auditing information in NHibernate.

I'm currently extending DefaultSaveOrUpdateEventListener, overriding PerformSaveOrUpdate, going through the properties of each entity and saving them elsewhere.

This works with simple properties, but fails when cascade-saving a one-to-many relationship.

If I take the following entities:

[ActiveRecord]
public class Child
{
    [PrimaryKey(PrimaryKeyType.GuidComb)]
    public Guid Id { get; set; }

    [BelongsTo]
    public Parent Parent { get; set; }
}

[ActiveRecord]
public class Parent
{
    [PrimaryKey(PrimaryKeyType.GuidComb)]
    public Guid Id { get; set; }

    [HasMany(Cascade = ManyRelationCascadeEnum.SaveUpdate)]
    public IList<Child> Children { get; set; }
}

And then save a parent with a child:

ActiveRecordMediator<Parent>.Save(new Parent
{
    Children = new List<Child>
    {
        new Child()
    }
});

The child will get the correct parent assigned to it when its persisted to the database but the 'Parent' property of the child is null when my EventListener is called.

How can I get the value that will actually be persisted to the database in this case?

[EDIT] I've recently been looking at getting this to work by hooking the cascade and seeing what else was being saved at the time, but that seems horribly unreliable and I'd much prefer to get the data out of NHibernate so I know it's consistent with the database.

A: 

I see that you use Castle ActiveRecord. I was experimenting with it also.

There is some weirdness in it, because in the code you provided, the Child object's Parent property will only be set after your stuff is saved to the database. Until then, its value will be null. (I don't know if this behaviour is specific to ActiveRecord, or also NHibernate.)

Perhaps if you assign the Parent properties of the Child objects by hand, it will work.

var parent = new Parent();
var child = new Child()
{
    Parent = parent
};
parent.Children.Add(child);

ActiveRecordMediator<Parent>.Save(child);
ActiveRecordMediator<Parent>.Save(parent);

Maybe the order in which you save the entities also has to do something with this matter.

Venemo
Yes and no. It works, but it works because NHibernate isn't doing the cascade saving - the developer is doing it. Unfortunately it would mean disabling cascade saving, which is pretty fundamental to a decent ORM. Thanks for the suggestion though.
Alun Harford
Actually, cascade saving works well, but there is the issue that a child doesn't get its parent set, until it is saved. (Or at least that was the case in ActiveRecord last time I tried.)Your event listener recieves the children before it happens.
Venemo
A: 

Hi Alun,

I don't use ActiveRecord, I use NHibernate instead so I'm going to assume that they handle parent-child relationships in the same way (https://www.hibernate.org/hib_docs/nhibernate/html/example-parentchild.html)

What happens if you leave the ORM to manage the link to the parent (by setting Inverse=true in the HasMany attribute)?

[ActiveRecord]
public class Parent
{
    [PrimaryKey(PrimaryKeyType.GuidComb)]
    public Guid Id { get; set; }

    [HasMany(Cascade = ManyRelationCascadeEnum.SaveUpdate, Inverse=true)]
    public IList<Child> Children { get; set; }
}
Benjamin Arroyo
Hi Ben. Alas, as far as I could tell that makes no difference.
Alun Harford
+2  A: 

I'm not sure how you can accomplish this with ActiveRecord but it has to do with the mechanism in which NHibernate persists parent/child relationships.

Saving the child cascade prior to saving the parent in NHibernate is by design depending on which end of the relationship is marked as "inverse=true" and the child needs to have a "not-null=true" attribute on the element (which determines which end owns the relationship). This will make it so the Child is managing the state of the relationship.

Then you can simply save the child, and the parent will be updated with the appropriate information. This will generate one INSERT statement, instead of an INSERT AND UPDATE that you are probably seeing now. Not sure if this solves your problem, but I believe the problem you are having is around this behavior. You can read more at this link:

https://www.hibernate.org/hib_docs/nhibernate/html/example-parentchild.html

Sean Chambers
Thanks - I'm not sure that solves my actual issue (Child.Parent is still null at the point where my listener is executed) but at least I understand a case where my code doing nasty hacks for cascade saving and I fixed a bug.
Alun Harford