views:

167

answers:

1

I have two related objects: ProgramSession and ProgramTask, with a one-to-many relationship. A ProgramSession has many ProgramTasks. So the objects looks like this:

public class ProgramSession
{
    public virtual IList<ProgramTask> ProgramTasks
    {
        get { return _programTasks; }
        set { _programTasks = value; }
    }
}

public class ProgramTask
{
    public virtual ProgramSession ProgramSession
    {
        get { return _programSession; }
        set { _programSession = value; }
    }
}

And the mappings...

ProgramSession.hbm.xml

<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
    <key column="SessionUid"></key>
    <one-to-many class="ProgramTask"></one-to-many>
</bag>

ProgramTask.hbm.xml

<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />

The problems begin when I try to change the ProgramSession of a ProgramTask.

If I remove the ProgramTask from the ProgramSession.ProgramTasks list property of the old session, and then add it to the same property on the new session, NHibernate tells me that the a deleted object will be resaved.

If I simply change the value of the ProgramTask.ProgramSession object, I have no problem saving. However, I get weird behavior if I do not save immediately because the ProgramSession.ProgramTasks properties (on both sessions) are not synchronized until after the NHibernate session is refreshed.

Changing the ProgramTask.ProgramSession object without directly modifying the lists as well, creates an invalid state. Take the following code as an example:

programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails

This is more problematic in code that executed later on, that assumes the ProgramTasks collections are synchronized with the ProgramSession property. For example:

foreach(var programTask in programSession.ProgramTasks)
{
    // whatever
}

One hack I used to work around this was to query the list. I can't use it everywhere, and it's clearly a bad solution, but it underscores the problem:

var tasksActuallyInSession =
    programSession.ProgramTasks
        .Where(task => task.ProgramSession == programSession)
        .ToList();

Is there any way to handle this kind of situation? A best practice? Am I doing something wrong? Is the mapping incorrect? Is there some super-secret NHibernate flag I need to set?

+1  A: 

Not sure if I understand everything what you are doing here. Some thoughts:

If you decide to move ProgramTasks around, then they get independent and should not be mapped using cascade="all-delete-orphan". If you do this, NH removes the ProgramTask from the database when you remove it from a ProgramSession.

Map it using cascade="none" and control the lifecycle of the object yourself. (this means: store it before a ProgramSession gets stored. Delete it when it is not used anymore.)

Not sure if this this is also a problem, but note that if you have a inverse reference, you code is responsible to make the references consistent. Of course, references get cleaned up after storing to the database and loading into an empty session, this is because there is only one foreign key in the database. But this is not the way it should be done. It is not responsibility of NH to manage your references. (Its only responsibility is to persist what you are doing in memory.) So you need to make it consistent in memory, and implement your business logic as if there wasn't NHibernate behind.

Stefan Steinegger
The cascade option did the trick. I did some more research and have settled on cascade="delete" on certain relationships. One caveat of this approach is that I now need to track which entities are new so I can decide whether to call Save() or Update() on the entity, whereas before I used SaveOrUpdate(). So this actually deepens my dependency on NHibernate, but it works. Thank you.
shovavnik