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

发布于 2024-10-05 01:23:04 字数 1980 浏览 3 评论 0原文

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

我有一个经典的 Parent 和 Child 实体,如下所示

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”中。这是正确的,因为该列不可为空,但为什么要插入空值?

如果我删除空约束,保存会起作用,因为 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 没有在子行的初始插入上设置外键以及如何解决这个问题?

I am having a problem with what should be a fairly simple (I would think) NHibernate use case.

I have a classic Parent and a Child entity like so:

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

And mappings as follows:

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

Lastly, I have a simple tests:

   [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();
                });

    }

The test fails with a System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ParentId'. This is correct in that the column is non-nullable but why is it inserting null?

If I remove the null constraint, the save works because NHibernate first inserts the parent, then inserts the child, then updates the ParentId column on the child record as shown in this output:

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

This seems bizarre to me as in almost all cases foreign key columns of this sort are declared non-nullable and hence the foreign key must be provided at insert. So why is NHibernate not setting the foreign key on the initial insert of the child row and how to I fix this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

雨落□心尘 2024-10-12 01:23:04

您的映射存在一些问题...您有一个双向关系,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()

A few problems with your mapping... You have a bidirectional relationship and NHibernate needs to know which way to update it. In the OO world, references only go one way and there is no way for NHibernate to know that Parent->Children is the same FK as Child->Parent. Right now you have Child->Parent set to ReadOnly(). This is telling NHibernate not to update this property. So it tries to insert the Child (with a null parent) and then update the FK from the Parent side. This isn't working if you have a not null constraint on your FK. The usual way to map this is to use Inverse=true on the parent side and let the child worry about persistence. (It's your job in the OO model to ensure that the Parent->Children collection contains the same set of references as the set of Child->Parent relationships.)

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()
    }
}

The SQL statements sent to the database when saving are now:

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

INSERT INTO [Child]
           (Name,
            ParentId)
VALUES     ('C1' /* @p0 */,
            1 /* @p1 */)
select SCOPE_IDENTITY()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文