在 NHibernate 中从 child 级联创建时如何获得新的 parent ID?

How do I get a new parent ID when cascade creating from a child in NHibernate?

我有一个 parent child 关系,其中 parent 没有对 children 的引用 - 像这样:

class Child
{
    int Id { get; set;
    Parent Parent { get; set; }
    // + other stuff
}

class Parent
{
    int Id { get; set; }
    // + other stuff
}

在数据库中,这仅表示 Child table 有一个 ParentID 列。此列可以为空 - 即可以有 Parent-less Child.

我正在使用 Fluent 约定来映射域,但是这个关系有一个覆盖,因此我们可以更新 Child 并同时创建一个新的 Parent :

public class ChildOverride : IAutoMappingOverride<Child>
{
    public void Override(AutoMapping<Child> mapping)
    {
        mapping.References(x => x.Parent).Cascade.All();
    }
}

当我们向现有的 Child 添加一个新的 Parent 并立即需要读取该 Parent 的 ID(在事务中)时,问题就出现了,例如:

existingChild.CreateParent(parameters);
session.Save(existingChild);
Debug.WriteLine(existingChild.Parent.Id);

只打印 0 而不是给我新的 Parent 的 ID - 我曾假设 Cascade.All() 会......好吧,级联。有错吗?

如果我在访问 Id 之前提交事务,一切都很好,或者如果我明确保存新的 Parent 也有效,例如

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
transaction.Commit();
Debug.WriteLine(existingChild.Parent.Id);

// This also works
existingChild.CreateParent(parameters);
session.Save(existingChild);
session.Save(existingChild.Parent);
Debug.WriteLine(existingChild.Parent.Id);

有什么方法可以更改我的覆盖,以便保存 child object 将允许立即访问 parent id? (或者我做错了什么?)

您使用的 ID 生成策略仅在对象命中数据库时生成 ID。如果您想在此之前拥有父 ID,则需要使用 assigned 策略将 ID 生成权掌握在自己手中。

这个话题很长,我不想在这里重复。您可以在此处了解您需要做什么: Don't Let Hibernate Steal Your Identity

文中ID类型为UUID。您可以轻松实现 intlong 数据类型的自定义 ID 生成器。

好处是,一切正常你的映射是正确的。因为如果此代码段有效...

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
transaction.Commit();

...这个概念也很有效。为什么?这是怎么回事?为什么 Commit() 帮助解决了这个问题?

嗯,因为ISession是一个抽象,它是虚拟持久存储。在我们调用 session.Save() 的那一刻,它 不会 执行任何 SQL 语句。它只保留所有信息 (在使用该会话期间收集) 并仅执行 SQL WRITE 语句

  • 如果迫切需要(DB 生成的 ID 需要进一步的东西)
  • 如果会话决定 (如果允许...请参阅下面的会话模式自动)
  • 如果明确要求

但更好更精确的是引用文档:

9.6. Flush

From time to time the ISession will execute the SQL statements needed to synchronize the ADO.NET connection's state with the state of objects held in memory. This process, flush, occurs by default at the following points

...

Except when you explicity Flush(), there are absolutely no guarantees about when the Session executes the ADO.NET calls, only the order in which they are executed. However, NHibernate does guarantee that the ISession.Find(..) methods will never return stale data; nor will they return the wrong data.

It is possible to change the default behavior so that flush occurs less frequently. The FlushMode class defines three different modes: only flush at commit time (and only when the NHibernate ITransaction API is used), flush automatically using the explained routine (will only work inside an explicit NHibernate ITransaction), or never flush unless Flush() is called explicitly. The last mode is useful for long running units of work, where an ISession is kept open and disconnected for a long time (see Section 11.4, “Optimistic concurrency control”).

这就是答案。 FlushMode 设置:

public enum FlushMode
{
    Unspecified = -1,
    Never = 0,
    Commit = 5,
    Auto = 10,
    Always = 20,
}

因此,如果 FlushMode 是 Commit - 只有事务提交会触发 session.Flush()

但我们随时可以自己做:

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
session.Flush();

现在父级将拥有 ID - 因为会话状态刚刚转换为 SQL WRITE 操作...