views:

286

answers:

2

Using NHibernate, I am looking for a way to automatically update persisted collections when a persisted item is deleted. For instance:

var post = GetNewPost();
var blog = GetCurrentBlog();
blog.Posts.Add(post);
BlogRepository.Update(blog);

User.Posts.Add(post);
UserRepository.Update(user);

----

// Somewhere else
var blog = GetCurrentBlog();
var post = blog.Posts.Last();
blog.Posts.Remove(post);

NHibSession.Flush(); // Throws an ObjectDeletedException due to 'post' 
                     // still being in the User.Posts collection

I understand that there may be issues with the model and/or mapping in this example, but those issues aside, I want to find a way to get the User.Posts collection to automatically be updated, i.e. remove 'post' from itself. (Maybe not the greatest example in the world, but assume there are many Blogs and many Users, unrelated to one another except through Posts. Understand that this is a façade.)

In this example, I'm using only one NHibernate session. I'm willing to adjust that, but I'm looking for a scheme, and it should be transparent to the model. NHibernate events are also on the table if it can be shown to be a good practice.

I believe that LINQ to SQL can handle this situation, so I'm a little surprised that NHibernate can't (to the best of my searching abilities). Are there any supplementary frameworks available which can?

Here are the relevant mappings for this example:

<class name="App.Core.Domain.Post, App.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Posts" xmlns="urn:nhibernate-mapping-2.2">
    <id name="Id" type="Guid" column="PostId">
        <generator class="assigned" />
    </id>
    ...
    <many-to-one cascade="save-update" access="field.pascalcase-underscore" name="Blog" column="BlogId" />

    <many-to-one cascade="save-update" access="field.pascalcase-underscore" name="User" column="UserId" />
</class>

<class name="App.Core.Domain.Blog, App.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Blogs" xmlns="urn:nhibernate-mapping-2.2">
    <id name="Id" type="Guid" column="BlogId">
        <generator class="assigned" />
    </id>
    ...
    <bag name="Posts" cascade="all" inverse="true" access="field.pascalcase-underscore">
        <key column="PostId" />
        <one-to-many class="App.Core.Domain.Post, App.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </bag>
</class>

<class name="App.Core.Domain.User, App.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Users" xmlns="urn:nhibernate-mapping-2.2">
    <id name="Id" type="Guid" column="UserId">
        <generator class="assigned" />
    </id>
    ...
    <bag name="Posts" cascade="all" inverse="true" access="field.pascalcase-underscore">
        <key column="PostId" />
        <one-to-many class="App.Core.Domain.Post, App.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </bag>
</class>
A: 

Try removing the cascade=all on the Posts bag. When you flush the session, Post collection cascading to the post and trying to update it.

You are creating a new post by using Blog.Posts.Add but trying to delete it using PostsRepository.Delete(post). Choose one strategy and stick to it.

reach4thelasers
I like your proposal, but Wes did a good job of explaining why it's not quite a solution. He adds a User.FavoritePosts collection, which is also persisted and also contains a reference to the Post to be removed. Using only one session, I would have to be meticulous about what collections referenced that post and check and remove it from *all* of them before I tried flushing the session, or I'll get the same ObjectDeletedException.
Tyson
A: 

I want to refine this question some more, say we extend the situation with the following new Model

What happens when our post is associated to another entity besides a Blog? Even if we take care to only remove the post via Blog.Posts.Remove, what is the general strategy for handling cleanup of other associations? Assume we cannot stuff all items into one aggregate root, if we could, we could clean them all up via the AR. So basically we have associations between aggregate roots, what is the best practice to clean those up? In this example, a user can list their favorite posts, but this is only a 1 way association from User to Post, there is no association back.

Would we need a domain service, to delete/remove an entity from the domain (not just make it transient but get it GCed), that would remove all associations? This will make NHibernate happy when it persists the changes. In this case we would need to remove a post from not only a blog but from any user that has it marked as a favorite. Should this live in a domain service, application service? How do we leverage this service if a cascade delete happens, say we delete a blog that has posts that are marked favorites, should we tap into NHib events and detect posts were deleted and cleanup associations?

I know some people suggest using FK cascading in the database but this will only work if associations are not loaded into a session. If they are loaded, they have to be removed. What I don’t like about this approach is that it violates the idea of having a real model in memory that acts as it would even if NHib and the database were not there. Thoughts?

Wes
What program do you use to make those diagrams?
reach4thelasers
http://yuml.me/
Tyson