【问题标题】:How do I get a new parent ID when cascade creating from a child in NHibernate?从 NHibernate 中的孩子级联创建时,如何获取新的父 ID?
【发布时间】:2015-03-28 23:52:50
【问题描述】:

我有一个父子关系,其中父级没有对子级的引用 - 像这样:

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

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

在数据库中,这仅仅意味着Child 表有一个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();
    }
}

当我们将新的Parent 添加到现有的Child 并立即需要读取该父级的 ID(在事务中)时,就会出现问题,例如:

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

这只是打印 0 而不是给我新父级的 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);

有什么方法可以更改我的覆盖,以便保存子对象将允许立即访问父 ID? (或者还有什么我做错了吗?)

【问题讨论】:

    标签: c# nhibernate fluent-nhibernate


    【解决方案1】:

    好处是,一切正常您的映射是正确的。因为如果这个 sn-p 工作...

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

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

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

    • 如果必须(更远的东西需要DB生成的ID)
    • 如果会话决定(如果允许...请参阅下面的会话模式自动)
    • 如果明确要求

    但更好、更准确的是引用文档:

    9.6. Flush

    ISession 将不时执行同步 ADO.NET 连接状态与内存中保存的对象状态所需的 SQL 语句。这个过程,flush,默认发生在以下几点

    ...

    除非您明确指出Flush()绝对不能保证 Session 何时执行 ADO.NET 调用,只能保证它们的执行顺序。 但是,NHibernate 确实保证 ISession.Find(..) 方法永远不会返回过时的数据;它们也不会返回错误的数据。

    可以更改默认行为以减少刷新频率。 FlushMode 类定义了三种不同的模式:仅在提交时刷新(并且仅在使用 NHibernate ITransaction API 时),使用解释的例程自动刷新(仅在显式 NHibernate ITransaction 内工作),或除非明确调用 Flush(),否则永远不要刷新。最后一种模式对于长时间运行的工作单元很有用,其中 ISession 长时间保持打开和断开连接(请参阅第 11.4 节,“乐观并发控制”)。

    这就是答案。 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 操作...

    【讨论】:

    • 这很有用,谢谢。不过,也许我应该更明确地说明我的困惑的原因 - 如果我使用相同的过程来保存 new 子对象,我可以立即获得孩子的 ID,但不能父母。这似乎是一个错误,或者至少是 NHibernate 行为的不一致。
    • 请不要将其视为不一致。这一切都是设计使然。我们应该将会话视为一个黑匣子。不要期望在此过程中它完全符合我们的期望。但保证一旦 FLUSH 被调用,所有的东西都会在数据库中正确更新。所以,不要解决这个问题,只需正确使用 open - close ...
    • 当根对象和叶对象的行为不同时,很难不将其视为不一致。我实际上也找不到任何记录这种行为的地方,除了说没有任何保证。
    • @adhocgeek,我不确定是否可以在这里改变你的想法,但我会尝试一次。 NHibernate 是一致的。 Session 是一种抽象,它做了很多聪明的事情。很多聪明的东西。它有它的界限。所以从我们打开它的那一刻起,直到我们调用 Flush——所有的变化/脏污/状态都是不可预测的。有些需要坚持,有些则不需要。但是我们此时不应该以其他方式与 Session 交互,然后完成更改 - 批处理。一旦 Flush 被调用,所有的东西都会被持久化。这也意味着:始终在不同的会话中拆分 READ 和 WRITE 操作...
    • 我刚刚查看了 NHibernate 源代码——它是否只保证刷新 SQL 以创建根对象,因为 Save(x) 必须返回一个 ID?我完全理解你所说的 Session 有它的界限 - 你必须在某个地方停下来:D
    【解决方案2】:

    您使用的 ID 生成策略仅在对象命中 DB 时生成 ID。如果您想在此之前拥有父 ID,则需要使用assigned 策略来生成 ID。

    这是一个很长的话题,我不想在这里重复。您可以在此处阅读有关您需要做什么的信息: Don't Let Hibernate Steal Your Identity

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 2018-04-19
      • 1970-01-01
      • 2022-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多