How to save a child with assigned id in nhibernate

I have two classes:

public class Parent
{
    public virtual long? ID { get; set; } // native
    public virtual IList<Child> Children { get; set; }
    public virtual string Name { get; set; }
}

public class Child
{
    public virtual long ID { get; set; } // assigned
    public virtual string Name { get; set; }
}

Instantiating and saving parent and child:

child = new Child() { ID = 1, Name = "SomeName" };
parent = new Parent() { Children = new List() { child } };
session.Save(parent);

Which gives me:

NHibernate.StaleStateException: Unexpected row count: 0; expected: 1.

I think the problem is with the assigned id on the child. Since it has an id, NHibernate thinks it has previously saved before which is not the case.

The generated (trimmed & renamed) SQL is:

NHibernate: select child0_.ID as child1_1_, child0_.NAME as NAME1_, child0_.PARENT_ID as COMMAND7_1_, from CHILD child0_
NHibernate: select parent0_.PARENT_ID as parent1_10_
NHibernate: select parent0_.PARENT_ID as parent1_10_, parent0_.NAME as parent2_10_ from PARENT parent0_
NHibernate: UPDATE CHILD SET PARENT_ID = @p0 WHERE CHILD_ID = @p1;@p0 = 2, @p1 = 1

Mapping files:

<class name="MyNamespace.Child" table="CHILD">
  <id name="ID" column="CHILD_ID" type="System.Int64">
    <generator class="assigned"></generator>
  </id>
  <property name="Name" column="NAME"></property>
</class>

<class name="MyNamespace.Parent" table="PARENT">
  <id name="ID" column="PARENT_ID" type="System.Int64">
    <generator class="native"></generator>
  </id>
  <property name="Name" column="NAME"></property>
  <bag name="Children">
    <key column="PARENT_ID"></key>
    <one-to-many class="MyNamespace.Child"></one-to-many>
  </bag>
</class>

While searching google, I found about version tag which may be a solution but I do not have a persistent field to use as version. In this case, how can I save (insert) a child with assigned id and its parent?


Solution 1:

When cascading from a parent to a child, NHibernate uses the SaveOrUpdate method. You are correct that NHibernate need some way to determine whether it should perform an insert or an update. It will look at three different fields for an unsaved value to determine if the entity is new.

  1. Id
  2. Version
  3. Timestamp

With an assigned Id, you will need either a Version or Timestamp field in order to indicate that the entity is new.

An alternative would be to call Save() on the children explicitly.

Solution 2:

I'm not 100% sure if this is the same problem you are having, but my database is 100% assigned id (ugh) and I had to create an Interceptor that kept track of whether a child is or isn't persisted for cascades to work.

The code is cut/paste (which is why it has dumb names... I didn't understand 100% at first!), and I initially got 90% of it from online documentation (which I can't find via google right now ... sorry):

Base class you put on object that has an assigned ID you want to cascade:

public class Persistent
{
    private bool _saved = false;

    public virtual void OnSave()
    {
        _saved = true;
    }

    public virtual void OnLoad()
    {
        _saved = true;
    }

    public virtual bool IsSaved
    {
        get { return _saved; }
    }
}

The interceptor you add to session:

public class TrackingNumberInterceptor : EmptyInterceptor
{
    public override bool? IsTransient(object entity)
    {
        if (entity is Persistent)
        {
            return !((Persistent)entity).IsSaved;
        }
        else
        {
            return null;
        }
    }

    public override bool OnLoad(object entity, object id, object[] state, string[] propertyNames, IType[] types)
    {
        if (entity is Persistent) ((Persistent)entity).OnLoad();
        return false;
    }

    public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
    {
        if (entity is Persistent) ((Persistent)entity).OnSave();
        return false;
    }
}

Basically the idea is that since NHibernate doesn't know if an assigned id entity is persisted or not, you keep track for it.

By default the object starts with persisted (_saved) at false. When the entity is either loaded or saved by NHibernate, the trigger sets the objects persisted (_saved) flag to true.

So for a fresh item that isn't persisted, it starts at false and stays false because NHibernate has never saved or loaded it. When NHibernate checks whether the child is transient, the trigger responds that it is transient, and a save happens which marks the child as persisted. Also now any future use will require a load which again marks it as persisted.