Семейная коллекция SaveOrUpdate для NHibernate не обновляется с идентификационными идентификаторами

Мне явно не хватает чего-то (надеюсь, очевидно), и до сих пор мне не повезло с Google.

У меня есть отношение родитель-ребенок, отображаемое следующим образом

Упрощенная родительская карта

  public sealed class ParentMap : ClassMap<ParentEntity>
  {
    public ParentMap()
    {
      Table("Parent");
      Component(x => x.Thumbprint);
      Id(x => x.Id).GeneratedBy.Identity();

      Map(x => x.ServeralNotNullableProperties).Not.Nullable();

      HasMany(x => x.Children).KeyColumn("ChildId")
                              .Inverse()
                              .LazyLoad()
                              .Cascade
                              .AllDeleteOrphan();

      References(x => x.SomeUnrelatedLookupColumn).Column("LookupColumnId").Not.Nullable();
    }
  }

Упрощенная карта ребенка

  public sealed class ChildMap : ClassMap<ChildEntity>
  {
    public ChildMap()
    {
      Table("Child");
      Component(x => x.Thumbprint);
      Id(x => x.Id).GeneratedBy.Identity();

      Map(x => x.MoreNotNullableProperties).Not.Nullable();

      References(x => x.Parent).Column("ParentId").Not.Nullable();
    }
  }

Шаги упрощенного сервисного метода

Затем у меня есть метод службы, который извлекает Parent и добавляет нового Child через некоторый метод домена. Основной код NHibernate сводится к следующему:

1) Сессия, открытая в WCF AfterReceiveRequest (IDispatchMessageInspector)

_sessionFactory.OpenSession();

2) Получить существующий экземпляр родителя через .Query

_session.Query<ParentEntity>()
        .Where(item => item.Id == parentId)
        .Fetch(item => item.SomeLookupColumn)
        .First();

3) Добавить новый объект "Child" в "Parent" с помощью метода объекта домена, например...

public ChildEntity CreateChild()
{
  var child = new ChildEntity{ Parent = this };

  Children.Add(child);

  return child;
}

4) В конечном итоге вызовы SaveOrUpdate на объекте Parent.

 // ChildEntity Id is 0
 _session.SaveOrUpdate(parentEntity)
 // Expect ChildEntity Id to NOT be 0

Примечание. Использование SaveOrUpdate Сохраняет изменения в базе данных; использование результатов слияния в TransientObjectException, упомянутых ниже.

5) Наконец, транзакция, выполненная в WCF BeforeSendReply (IDispatchMessageInspector)

 _session.Commit();

Проблема

Обычно, когда объект сохраняется, после вызова SaveOrUpdate Идентификатор упомянутого объекта определяет идентификатор, сгенерированный оператором INSERT. Однако, когда я добавляю новый дочерний объект к существующему родительскому объекту, связанный объект в Children не получает идентификатор. Мне нужно передать этот идентификатор обратно вызывающему, поэтому последующие вызовы приводят к UPDATES, а не дополнительным INSERTS.

Почему NHibernate не обновляет базовый объект? Должен ли я явным образом вызывать SaveOrUpdate для дочернего элемента (что отстойно, если это так)?

Любые мысли по этому поводу получили высокую оценку. Спасибо!

ИЗМЕНИТЬ Следует отметить, что новый дочерний объект IS сохраняется как ожидалось; Я просто не возвращаю идентификатор.

UPDATE Попробовал переключиться на .Merge(~), как предложил Майкл Буэн ниже; в результате чего TransientObjectException говорит мне явно сохранить измененный дочерний объект перед вызовом слияния. Я также экспериментировал с .Persist(~) безрезультатно. Любое дополнительное понимание очень ценится.

объект - несохраненный экземпляр переходного процесса - сохраните временный экземпляр перед слиянием: My.NameSpace.ChildEntity

Ответ 1

Наконец-то я нашел время, чтобы копаться в коде NHibernate... причина, по которой это происходит, имеет смысл... короткий ответ - действия Cascade обрабатываются на Session.Commit(); после завершения вышеуказанной сервисной операции. Таким образом, мои дочерние объекты не обновлялись, чтобы отражать назначенные идентификаторы к моменту, когда метод службы возвращает обновленный дочерний объект.

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

Ответ 2

Для снятого сценария используйте Merge (ранее известный как SaveOrUpdateCopy)

Пример: http://www.ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html

Относительно:

Мне явно нужно позвонить SaveOrUpdate для ребенка (что отстойно если это так)?

На самом деле вам не нужно, чтобы там, где ORM, например NHibernate, светит, он может сохранить весь графический объект, и он действительно решает несоответствие импеданса между объектом и базой данных.

Физическая реализация (например, int или guid) того, как связаны записи родителя и ребенка (и записи дочерних элементов и т.д.), полностью невидима из приложения. Вы можете хорошо сосредоточиться на своих объектах, а не на подкладке, на каких типах данных (int, guid и т.д.) Ваши объектные отношения должны использовать

[ОБНОВЛЕНИЕ]

Вы все равно можете сохранить родителя, и сохранение будет каскадом для детей, а также может получить идентификатор ребенка. Если вам нужно получить идентификатор ребенка, получите его после фиксации.

Вы не можете получить дочерний идентификатор таким образом:

s.SaveOrUpdate(parentEntity);
Console.WriteLine("{0}", parentEntity.ChildIdentity.First().ChildId);
tx.Commit();

Должно сделать следующее:

s.SaveOrUpdate(parentEntity);
tx.Commit();    
Console.WriteLine("{0}", parentEntity.ChildIdentity.First().ChildId);

Ответ 4

Вы можете вызвать session.Flush(), который выполняет вставки в базе данных. Это не идеально по причине воздействия на производительность.