In our latest project, our team made use of NHibernate to act as an ORM (Object-Relational Mapper) and to handle all of our data access work.
In the current phase that we are working on, we’re running into a problem. We can’t get NHibernate to delete a child object in a collection on a parent.
Here’s the basic situation:
We have a Proposal, which has a collection of ProposalMilestones. The ProposalMilestone has a reference to the parent Proposal, as well as a reference to the Milestone that it refers to.
When trying to remove a ProposalMilestone from the collection and flushing the session, NHibernate throws a StaleObjectException.
Here are images of the class diagram for this section and the database schema that applies:
Nothing special about either one. The relationships are as would be expected.
Here are the related mapping files:
Proposal.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CIV.Entities" assembly="NHSample" default-access="nosetter.camelcase" default-lazy="false"> <class name="CIV.Entities.Proposal, NHSample" table="Proposal"> <id name="Id" column="ID"> <generator class="guid.comb" /> </id> <version name="Version" column="Version" type="CIV.Entities.Timestamp, NHSample" generated="always" /> <component name="Audit" class="CIV.Entities.Audit, NHSample"> <property name="CreatedBy" column="CreatedBy"/> <property name="CreatedOn" column="CreatedOn" /> <property name="ModifiedBy" column="ModifiedBy" /> <property name="ModifiedOn" column="ModifiedOn" /> </component> <property name="Code" column="Code" /> <property name="Description" column="Description" /> <property name="Name" column="Name" /> <property name="ProposedStartDate" column="ProposedStartDate" /> <bag name="Milestones" table="ProposalMilestone" inverse="true" cascade="all"> <key column="ProposalID" /> <one-to-many class="CIV.Entities.ProposalMilestone, NHSample" /> </bag> </class> </hibernate-mapping>
ProposalMilestone.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CIV.Entities" assembly="NHSample" default-access="nosetter.camelcase" default-lazy="false"> <class name="CIV.Entities.ProposalMilestone" table="ProposalMilestone"> <id name="Id" column="ID" unsaved-value="00000000-0000-0000-0000-000000000000"> <generator class="guid.comb" /> </id> <version name="Version" column="Version" type="CIV.Entities.Timestamp, NHSample" generated="always" /> <component name="Audit" class="CIV.Entities.Audit, NHSample"> <property name="CreatedBy" column="CreatedBy"/> <property name="CreatedOn" column="CreatedOn" /> <property name="ModifiedBy" column="ModifiedBy" /> <property name="ModifiedOn" column="ModifiedOn" /> </component> <property name="OriginalDueDate" column="OriginalDueDate" type="DateTime" /> <many-to-one name="Milestone" column="MilestoneID" class="CIV.Entities.Milestone, NHSample"/> <many-to-one name="Proposal" column="ProposalID" not-null="true" class="CIV.Entities.Proposal, NHSample" /> <bag name="Notifications" inverse="true" cascade="all"> <key column="ProposalMilestoneID" /> <one-to-many class="CIV.Entities.Notification, NHSample" /> </bag> </class> </hibernate-mapping>
Milestone
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHSample" namespace="CIV.Entities" default-access="nosetter.camelcase" default-lazy="false"> <class name="CIV.Entities.Milestone, NHSample" table="Milestone"> <id name="Id" column="ID"> <generator class="guid.comb"/> </id> <version name="Version" column="Version" type="CIV.Entities.Timestamp, NHSample" generated="always" /> <component name="Audit" class="CIV.Entities.Audit, NHSample"> <property name="CreatedBy" column="CreatedBy" type="String" /> <property name="CreatedOn" column="CreatedOn" type="DateTime" /> <property name="ModifiedBy" column="ModifiedBy" type="String" /> <property name="ModifiedOn" column="ModifiedOn" type="DateTime" /> </component> <property name="Name" column="Name" type="String" not-null="true" /> <property name="Code" column="Code" type="String" not-null="true" /> <property name="Description" column="Description" type="String" /> </class> </hibernate-mapping>
Any suggestions would be greatly appreciated.
As a side note, I think my relations expressed in the mapping could be better. ProposalMilestone is being treated as an entity right now, but there is no reason (unless NHibernate requires it for the relationships) that it needs to be. I believe Milestone is a value object, but again, it’s being treated as an entity.
Suggestions here would also be appreciated.
Updated (3.29.08)
I did get this problem solved: The final thing that worked for me was putting the optimistic-lock=”false” tag on the collection declaration (Proposal). This avoided updating the Proposal before the collection of ProposalMilestones was updated, which was causing my stale object error.