Как сохранить ребенка с назначенным идентификатором в nhibernate

У меня есть два класса:

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; }
}

Создание и сохранение родителя и ребенка:

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

Что дает мне:

NHibernate.StaleStateException: неожиданное количество строк: 0; ожидается: 1.

Я думаю, что проблема с назначенным идентификатором на ребенка. Поскольку он имеет идентификатор, NHibernate думает, что ранее сохранял ранее, а это не так.

Сгенерированный (обрезанный и переименованный) SQL:

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

Файлы сопоставления:

<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>

При поиске в Google я нашел тег версии, который может быть решением, но у меня нет постоянного поля для использования в качестве версии. В этом случае, как я могу сохранить (вставить) дочерний элемент с назначенным идентификатором и его родителем?

3 ответа

Решение

При каскадном переходе от родителя к потомку NHibernate использует метод SaveOrUpdate. Вы правы, что NHibernate нужен какой-то способ определить, должен ли он выполнять вставку или обновление. Он рассмотрит три разных поля для несохраненного значения, чтобы определить, является ли объект новым.

  1. Я бы
  2. Версия
  3. Отметка

С назначенным идентификатором вам потребуется поле Version или Timestamp, чтобы указать, что объект является новым.

Альтернативой может быть явный вызов метода Save() для дочерних элементов.

Я не уверен на 100%, если это та же проблема, что и у вас, но моей базе данных присвоен 100% идентификатор (тьфу), и мне пришлось создать перехватчик, который бы отслеживал, сохраняется ли ребенок в каскадах или нет работать.

Код вырезан / вставлен (именно поэтому он имеет тупые имена... Сначала я не понял 100%!), И я первоначально получил 90% его из онлайн-документации (которую я не могу найти через Google права сейчас... извините)

Базовый класс, который вы помещаете в объект с назначенным идентификатором, который хотите каскадировать:

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; }
    }
}

Перехватчик, который вы добавляете в сессию:

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;
    }
}

По сути, идея заключается в том, что, поскольку NHibernate не знает, сохраняется ли назначенный объект id или нет, вы отслеживаете его.

По умолчанию объект начинается с постоянного (_saved) в false. Когда объект загружается или сохраняется NHibernate, триггер устанавливает флаг постоянных объектов (_saved) в значение true.

Таким образом, для нового элемента, который не был сохранен, он начинается с false и остается false, потому что NHibernate никогда не сохранял и не загружал его. Когда NHibernate проверяет, является ли дочерний процесс временным, триггер отвечает, что он временный, и происходит сохранение, которое отмечает дочерний элемент как сохраненный. Также теперь любое будущее использование потребует загрузки, которая снова помечает его как сохраненный.

Вызов session.SaveOrUpdate( childObject) должен решить проблему.

Другие вопросы по тегам