Colm Prunty
Colm Prunty

Reputation: 1620

NHibernate SaveOrUpdate merges child collections

I have a class Journal, which has an IList of JournalLine objects.

I created a Journal and accidentally ran it through the same line generation method twice. The start of this method calls _journalLines.Clear() and the end does _session.SaveOrUpdate(journal). So I have this sequence:

  1. Generate Journal with no lines, call SaveOrUpdate. That's fine.
  2. Generate three JournalLines with IDs 1,2,3, add to Journal._journalLines and call SaveOrUpdate
  3. Call Journal._journalLines.Clear(). A breakpoint after this shows the lines list to be empty.
  4. Generate three JournalLines with IDs 4,5,6, add to Journal._journalLines and call SaveOrUpdate. A breakpoint here shows _journalLines to have three things in it.
  5. I'm left with a Journal that has 6 lines.

This all takes place in one transaction and nothing is persisted in the database until it's finished.

Why is this merging the two collections? It seems to me that it should get to the last SaveOrUpdate where the breakpoint shows it's got three lines, and persist it as having three lines. Are the other three hanging around in memory somewhere because there hasn't been a flush yet?

Edit: Mapping

public JournalMap()
{
    // Other stuff
    HasMany(x => x.JournalLines)
        .Access.CamelCaseField(Prefix.Underscore)
        .Cascade.AllDeleteOrphan();
}

public JournalLineMap()
{
    // Other stuff
    References(x => x.Journal);
} 

Journal has these:

private readonly IList<JournalLine> _journalLines = new List<JournalLine>();
public virtual IEnumerable<JournalLine> JournalLines 
                                       { get { return _journalLines; } }

The actual code that generates the lines is way too complicated to add here, but it calls _journalLines.Add(journalLine); after generating them, and then calls this, T being a Journal

 public T Add(T entity)
 {
     _session.SaveOrUpdate(entity);
     return entity;
 }

Before ultimately calling _session.Flush() and _session.Transaction.Commit(); if the flush doesn't error.

Upvotes: 2

Views: 860

Answers (1)

Radim K&#246;hler
Radim K&#246;hler

Reputation: 123861

This issue could be related to the style of Journal mapping. Mostly the cascade setting used for your collection. If you are having cascade setting like save-update or all, e.g.

<bag name="JournalLines" lazy="true" inverse="true" cascade="save-update"
 ...

Then what is happening (using similar step numbers):

  1. ...
  2. At this point, the collection contains Items 1,2,3 ... SaveOrUpdate(jurnal) will do the cascade. It also does the cascading, i.e. session is aware about these 3 items to be persisted
  3. Clear() will clear the IList<> but there is no cascade trigger to remove these items from a session. At this moment, our first 3 JournalLines are really orphans. No one cares about them
  4. ...
  5. Flush() triggered by transaction will isnsert all the JournalLines into DB

Solution: change the mapping to

cascade="all-delete-orphan"

NOTE: from definiton you've provided above, I guess that this is the scenario (I just explained) Other issue you could be, if you call session.SaveOrUpdate(eachJournalLine). In that case Clear() is not enough, you also have to iterate all removed items and set their relation to line.Journal = null

Upvotes: 1

Related Questions