NHibernate 在二次更新中设置外键,而不是在初始插入时设置外键,违反了键列上的非空约束

2023-11-24

我对一个相当简单(我认为)的 NHibernate 用例有疑问。

我有一个经典的父实体和子实体,如下所示:

public class Parent 
{
    public virtual int ParentId { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Child> Children { get; set; }
}

public class Child
{
    public virtual int ChildId { get; set; }
    public virtual Parent Parent { get; set; }
    public virtual string Name { get; set; }
}

以及映射如下:

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Id(x => x.ParentId).GeneratedBy.Native();
        Map(x => x.Name);
        HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate();
    }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Id(x => x.ChildId).GeneratedBy.Native();
        Map(x => x.Name);
        References(x => x.Parent).Column("ParentId").ReadOnly().Not.Nullable();
    }
}

最后,我有一个简单的测试:

   [Test]
    public void Test_save_family()
    {
        var parent = new Parent();
        var child = new Child {Parent = parent};
        parent.Children = new List<Child>{child};

        SessionManager.WithSession(
            session =>
                {
                    session.Save(parent);
                    session.Flush();
                });

    }

测试失败并出现 System.Data.SqlClient.SqlException :无法将 NULL 值插入列“ParentId”中。这是正确的,因为该列不可为空,但为什么要插入空值?

如果我删除 null 约束,则保存会起作用,因为 NHibernate 首先插入父记录,然后插入子记录,然后更新子记录上的 ParentId 列,如以下输出所示:

NHibernate: INSERT INTO [Parent] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL
NHibernate: INSERT INTO [Child] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL
NHibernate: UPDATE [Child] SET ParentId = @p0 WHERE ChildId = @p1;@p0 = 2, @p1 = 1

这对我来说似乎很奇怪,因为在几乎所有情况下,此类外键列都被声明为不可为空,因此必须在插入时提供外键。那么为什么 NHibernate 没有在子行的初始插入上设置外键以及如何解决这个问题?


您的映射存在一些问题...您有一个双向关系,NHibernate 需要知道以哪种方式更新它。在面向对象的世界中,引用只有一种方式,NHibernate 无法知道 Parent->Children 与 Child->Parent 是同一个 FK。现在您已将 Child->Parent 设置为 ReadOnly()。这是告诉 NHibernate 不要更新这个属性。因此它尝试插入子级(父级为空),然后从父级更新 FK。如果您的 FK 具有非空约束,则此方法不起作用。通常的映射方法是在父端使用 Inverse=true 并让子端担心持久性。 (在 OO 模型中,您的工作是确保 Parent->Children 集合包含与 Child->Parent 关系集相同的引用集。)

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Id(x => x.ParentId).GeneratedBy.Native();
        Map(x => x.Name);
        HasMany(x => x.Children).KeyColumn("ParentId").Inverse().Cascade.SaveUpdate();
    }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Id(x => x.ChildId).GeneratedBy.Native();
        Map(x => x.Name);
        References(x => x.Parent).Column("ParentId").Not.Nullable();  // Removed ReadOnly()
    }
}

保存时发送到数据库的SQL语句现在是:

INSERT INTO [Parent]
           (Name)
VALUES     ('P1' /* @p0 */)
select SCOPE_IDENTITY()

INSERT INTO [Child]
           (Name,
            ParentId)
VALUES     ('C1' /* @p0 */,
            1 /* @p1 */)
select SCOPE_IDENTITY()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NHibernate 在二次更新中设置外键,而不是在初始插入时设置外键,违反了键列上的非空约束 的相关文章

随机推荐