views:

363

answers:

3

First the mapping...

<set name="Comments" table="cm_events_venue_comment" inverse="true"
        lazy="true" generic="true" cascade="all-delete-orphan"
        batch-size="10" order-by="dateCreated ASC">
    <cache usage="read-write" />
    <key column="venueId" />
    <one-to-many class="VenueComment" />
</set>

A passing test..

    [Test]
    public void CanSaveAndDeleteComments()
    {
        User u = TestDataHelper.CreateUser("Sir", "Talkalot");
        VenueComment comment;
        Venue v = GetVenueById();

        PerformInTransaction(() =>
        {
            userRepository.SaveOrUpdate(u);
            comment = new VenueComment()
            {
                Commenter = u,
                Text = "I like ifs and buts and i cannot lie..",
                DateCreated = DateTime.Now
            };
            v.AddComment(comment);
            comment = new VenueComment()
            {
                Commenter = u,
                Text = "And words ending in 'ly'",
                DateCreated = DateTime.Now
            };
            v.AddComment(comment);
            Assert.DoesNotThrow(()=>venueRepository.SaveOrUpdate(v));
        });

        // Test retrieval 
        Session.Evict(v);

        v = GetVenueById();
        v.Comments.Count.ShouldEqual(2);
        var c = v.Comments.FirstOrDefault<VenueComment>();
        c.Commenter.Id.ShouldEqual(u.Id);

        // and deletion
        PerformInTransaction(() =>
        {
            v.RemoveComment(c);
        });

        Session.Evict(v);
        v = GetVenueById();
        v.Comments.Count.ShouldEqual(1);

    }

My problems comes in the controller of my app :

    [Authenticate, AcceptAjax]
    public ActionResult DeleteComment(int id)
    {
        return PerformInTransaction<ActionResult>(() =>
            {
                var comment = commonDao.GetObjectById<VenueComment>(id);
                if (comment == null)
                    return JsonFailure("Comment not found");

                if (comment.Commenter.Id == CurrentUser.Id ||
                    AuthUtil.IsAdministrator()) //
                {

                   var venue = comment.Venue;
                   venue.RemoveComment(comment);

                    return JsonSuccess(id);
                }
                return JsonFailure("You can only delete comments you created.");
            });
    }

According to Log4Net, no deletes were issued. As i said the above test passes, so the mapping should be correct...

Any clues ?

+1  A: 

In my experience, this happens when I forget to either commit the transaction or properly close down & flush the session. Session & transaction management is also a notable difference between your test and your presenter.

John Rayner
Thanks, that gives me an idea of what to check. Will report back in a few...
JBland
Your comment lead me to some screwy session handling code, so thanks. See my answer...
JBland
+2  A: 

If you take a look at the documentation for the inverse attribute on collection-type property mappings, you will find that setting inverse="false" instructs NHibernate to watch the parent object (the object containing the collection) for changes to the collection, and to insert/delete child objects based on adds/removes on the parent's children-collection.

But, when you set inverse="true", you are instructing NHibernate to watch the child object for changes to the reference property back to the parent object. So when you set the child's parent-reference property, NHibernate will then go and modify the association.

It looks like you do not want to set inverse="true" on the parent's children-collection.

Addendum:

RemoveComment may disassociate the parent from the child (as in, set child.Parent = null). But if it doesn't also disassociate the child from the Session, and if the collection is marked inverse="true" indicating that the child owns the association rather than the collection owning the association, then NHibernate will not send down a delete. NHibernate will only send down a delete if the object is supposed to be deleted - a) if a collection owns the association (inverse="false") and the collection's cascading is set to include deletions and the object is removed from the collection, or b) if the object is deleted from the Session.

Justice
The strange thing is that the test passes fine with the mapping as is.
JBland
`RemoveComment` may disassociate the parent from the child (as in, set `child.Parent = null`). But if it doesn't also disassociate the child from the `Session`, and if the collection is marked `inverse="true"` indicating that the child owns the association rather than the collection owning the association, then NHibernate will not send down a `delete`. NHibernate will only send down a `delete` if the object is supposed to be deleted - if a collection owns the object (`inverse="false"`) and the object is removed from the collection, or if the object is deleted from the `Session`.
Justice
A: 

Mystery Solved.... It was a case of the merging of two code bases..

One older dao (commonDao) had a bug where the internal Session property was creating a new session from the session factory per property access, so there was a different session between the object load and flush().

The newer code uses repositories based on S#arp Architecture, which has automatic session management, so the problem did not appear in the unit tests.

JBland