I've put together a small test-case that replicates a problem one of our devs is having.
We have a Root object which has a collection of Nodes. Some of these may be members of a directed graph, so each Node has a collection of a its outgoing edges and its incoming edges: (apologies for the ascii art)
Code:
+--------+ +--------+ 1 from * +--------+
| | 1 * | |--------------| |
| Root |----------| Node | 1 to * | Edge |
| | | |--------------| |
+--------+ +--------+ +--------+
Ideally, we would like to save the whole lot by just saving the Root instance, but this is failing. NH is trying to do this:
- insert Root
- insert Node1
- insert Edge1
- insert Node2
- update Edge1 with FK to Node2
Unfortunately, the Edge-Node FKs have not-null constraints, so the initial insert of Edge2 fails.
Obviously, there are various work-arounds, but none of them feel
right:
- get rid of not null constraints
- flush the session before creating the Edge object
- disable the cascades and save the instances explicitly
- (on Oracle) make the not-null constraint deferred so it is only checked a the end of the transaction. Unfortunately, this project is using SQL Server, so this isn't an option.
Is there any way of getting NH to insert both Nodes before the Edge that links them?
Hibernate version: 1.2.0
Mapping documents:Code:
<?xml version="1.0" encoding="utf-8"?>
<!--Generated from NHibernate.Mapping.Attributes on 2007-11-28 12:06:20Z.-->
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="WireTest.Root, WireTest" proxy="WireTest.IRoot, WireTest" table="Root">
<id column="Id" type="Int32">
<generator class="native" />
</id>
<version name="VersionNumber" />
<property name="Name" length="20" not-null="true" unique="true" />
<bag name="Nodes" cascade="save-update" inverse="true">
<key column="RootId" />
<one-to-many class="WireTest.Node, WireTest" />
</bag>
</class>
<class name="WireTest.Node, WireTest" proxy="WireTest.INode, WireTest">
<id column="Id" type="Int32">
<generator class="native" />
</id>
<version name="VersionNumber" />
<property name="Label" length="20" not-null="true" />
<many-to-one name="Root" class="WireTest.Root, WireTest" column="RootId" not-null="true" />
<bag name="FromEdges" cascade="save-update" inverse="true" optimistic-lock="false">
<key column="FromNodeId" />
<one-to-many class="WireTest.Edge, WireTest" />
</bag>
<bag name="ToEdges" cascade="save-update" inverse="true">
<key column="ToNodeId" />
<one-to-many class="WireTest.Edge, WireTest" />
</bag>
</class>
<class name="WireTest.Edge, WireTest" proxy="WireTest.IEdge, WireTest">
<id column="Id" type="Int32">
<generator class="native" />
</id>
<version name="VersionNumber" />
<property name="Label" length="20" not-null="true" />
<many-to-one name="FromNode" class="WireTest.Node, WireTest" column="FromNodeId" not-null="true" />
<many-to-one name="ToNode" class="WireTest.Node, WireTest" column="ToNodeId" not-null="true" />
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():Code:
using (ISession session = _sessionFactory.OpenSession())
using(ITransaction transaction = session.BeginTransaction())
{
Root r = new Root();
r.Name = "a root";
INode n1 = r.AddNode("Node1");
INode n2 = r.AddNode("Node2");
IEdge edge = r.AddLink(n1, n2, "Edge 1");
try
{
session.Save(r);
transaction.Commit();
}
catch(HibernateException)
{
transaction.Rollback();
}
}
Full stack trace of any exception that occurs:Code:
at NHibernate.Impl.SessionImpl.CheckNullability(Object[] values, IEntityPersister persister, Boolean isUpdate)
at NHibernate.Impl.SessionImpl.DoSave(Object theObj, EntityKey key, IEntityPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IEntityPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(Object obj)
at NHibernate.Engine.Cascades.CascadingAction.ActionSaveUpdateClass.Cascade(ISessionImplementor session, Object child, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, Object child, IType type, CascadingAction action, CascadeStyle style, CascadePoint cascadeTo, Object anything)
at NHibernate.Engine.Cascades.CascadeCollection(CascadingAction action, CascadeStyle style, CollectionType collectionType, IType elemType, Object child, CascadePoint cascadeVia, ISessionImplementor
session, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, Object child, IType type, CascadingAction action, CascadeStyle style, CascadePoint cascadeTo, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, IEntityPersister persister, Object parent, CascadingAction action, CascadePoint cascadeTo, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object theObj, EntityKey key, IEntityPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IEntityPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(Object obj)
at NHibernate.Engine.Cascades.CascadingAction.ActionSaveUpdateClass.Cascade(ISessionImplementor session, Object child, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, Object child, IType type, CascadingAction action, CascadeStyle style, CascadePoint cascadeTo, Object anything)
at NHibernate.Engine.Cascades.CascadeCollection(CascadingAction action, CascadeStyle style, CollectionType collectionType, IType elemType, Object child, CascadePoint cascadeVia, ISessionImplementor
session, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, Object child, IType type, CascadingAction action, CascadeStyle style, CascadePoint cascadeTo, Object anything)
at NHibernate.Engine.Cascades.Cascade(ISessionImplementor session, IEntityPersister persister, Object parent, CascadingAction action, CascadePoint cascadeTo, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object theObj, EntityKey key, IEntityPersister persister, Boolean replicate, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.DoSave(Object obj, Object id, IEntityPersister persister, Boolean useIdentityColumn, CascadingAction cascadeAction, Object anything)
at NHibernate.Impl.SessionImpl.SaveWithGeneratedIdentifier(Object obj, CascadingAction action, Object anything)
at NHibernate.Impl.SessionImpl.Save(Object obj)
Name and version of the database you are using:
SQL Server 2005