Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate - Cascade Merge to child entities fails for detached parent entity

Current Approach

In an ASP.NET web forms app (using Spring.NET and NHibernate) we have an aggregate root (Person) whose details are captured across a number of screens/pages. The Person entity exists prior to entering into this workflow, and all changes made to the Person object graph are atomic, and so should only be flushed to database upon submission of the final screen.

To achieve this, we load the Person (lazily) from the database using NHibernate 3.2 the first time into the first page, and thereafter we load and save the serialized Person object graph to a HTTP Session variable as we page through the process.

After retrieving the Person out of the HTTP Session, it is in a detached state from the current NHibernate session, so we re-attach by invoking the Update() method on the current session, like so:

var sessionPerson = Session[PersonSessionName] as Person;
var currentSession = SessionFactory.GetCurrentSession();
currentSession.Update(sessionPerson);

Note: Using Lock() threw an exception, advising that the “reassociated object has dirty collection”.

When reattached, we can traverse through the object graph as expected, pulling data from the database for child entities which had not yet been loaded into memory.

Subset of Mapping Files

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="false" assembly="Domain" namespace=" TestApp.Domain">
  <class name="Person" table="Person">
    <id name="Id">
      <generator class="TestApp.CustomNHibernateHiLoGenerator, TestApp.Core" />
    </id>
    <property name="Name" not-null="false" />

    <bag name="PersonCountries" access="field.camelcase-underscore" cascade="all-delete-orphan">
      <key column="PersonId" foreign-key="FK_ PersonCountry_Person" not-null="true" />
      <one-to-many class="PersonCountry" />
    </bag>
  </class>

  <class name="Country" table="Country">
    <id name="Id">
      <generator class="TestApp.CustomNHibernateHiLoGenerator, TestApp.Core" />
    </id>
    ... No back reference to Person
  </class>
</hibernate-mapping>

Domain

public class PersonCountry : Entity, ICloneable
{
    // No properties of note
}

public class Person : Entity, ICloneable
{
    public virtual string Name { get; set; }
    public virtual IEnumerable<PersonCountry> PersonCountries { get; set; }
    ... 
    // More Properties
}

Flushing changes to database

.. // Code-behind
PricingService.Save(ProductContext.Pricing, forceMerge: true);            


public class PricingService : IPricingService
{
   [Transaction]  // Spring.NET transaction
   public Pricing Save(Pricing pricing, bool forceMerge = false)
   {            
      if(forceMerge)
      {
         CurrentSession.Merge(entity);
      }
      else
      {
         CurrentSession.SaveOrUpdate(entity);
      }
   }
}

When it comes time to flush all changes to the database, provided we only change Name, the change works as expected. However, adding a new Country item to Person causes the cascading of the Merge() on one-to-many relationships to fail with the following exception (oddly, removing a Country works fine).

NHibernate.StaleStateException: Batch update returned unexpected row count from update; actual row count: 0; Expected: 1

Any help would be greatly appreciated.

like image 641
RuairiQ Avatar asked Dec 07 '12 16:12

RuairiQ


1 Answers

every entity with a valid Id is viewed as persistent, thats why it tries to update it in merge but because it hasn't been saved yet it fails. Call session.Flush() after session.Update()

like image 177
Firo Avatar answered Oct 29 '22 01:10

Firo