理解外键not-null = true和反向行为与NHibernate的零对一关系

时间:2011-05-31 00:17:49

标签: nhibernate nhibernate-mapping

我正在尝试让NHibernate使用集合的许多方面来管理双向关联以建立零对一关系。

父类和地图:

public class Parent
{
    private ICollection<Child> children;
    public Parent()
    {
        this.children = new HashedSet<Child>();
    }
    public virtual Guid Id { get; protected internal set; }
    public virtual Child Child
    {
        get { return children.FirstOrDefault(); }
        set
        {
            {
                this.children.Clear();
                if (value != null)
                {
                    this.children.Add(value);
                }
            }
        }
    }
}

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        this.Id(x => x.Id)
            .GeneratedBy.GuidComb();
        this.HasMany<Child>(Reveal.Member<Parent>("children"))
            .Access.Field()
            .Cascade.All()
            .Not.Inverse()
            .AsSet();
    }
}

儿童类和地图:

public class Child
{
    public virtual Guid Id { get; protected internal set; }
    public virtual Parent Parent { get; set; }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        this.Id(x => x.Id)
            .GeneratedBy.GuidComb();
        this.References(x => x.Parent)
            .Not.Nullable()
            .Cascade.All();
    }
}

以下代码生成两个插入和更新:

var parent = new Parent();
var child = new Child();
parent.Child = child;
child.Parent = parent;
session.Save(parent);
session.Flush();

注意第二个插入和以下更新的基本上重复的SQL:

exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401'
go
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go
exec sp_executesql N'UPDATE [Child] SET Parent_id = @p0 WHERE Id = @p1',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go

虽然此代码产生臭名昭着的not-null property references a null or transient value inverse

var parent = new Parent();
var child = new Child();
parent.Child = child;
//child.Parent = parent;
session.Save(parent);
session.Flush();

我发现了很多关于这方面的帖子,但是还没有找到关于如何在inverse=false方面one进行零对一的明确指南。

我尝试过here提到的一对多/一对一方法。

同样,我在NHibernate上发现了几个关于(不)可以为空的外键的公开问题:NH-941NH-1050等。

我做错了什么?

编辑2011-05-30

所以,我的临时解决方案是在多方面采用标准的inverse=true设置,并在父设置器中做一些魔术:

public virtual Child Child
{
    get { return children.FirstOrDefault(); }
    set
    {
        {
            this.children.Clear();
            if (value != null)
            {
                value.Parent = this;
                this.children.Add(value);
            }
        }
    }
}

但我仍然对inverse=false行为感到困惑,这种行为在多对一方面应该相当于inverse=true(有趣的是,FluentNhibernate不允许设置ManyToOnePart inverse=true this文章推荐)。

1 个答案:

答案 0 :(得分:31)

何时使用inverse =“true | false”

inverse属性用于帮助NHibernate知道应该使用关系的哪一方来保持关系。非反面(请注意双重否定)是将持续存在的一面。如果任何一方都不是反向的,那么关系将持续两次,就像INSERT之后立即跟随您提供的UPDATE示例一样。如果双方都是反向的,那么关系就不会持续存在,所以正确设置反转很重要。

我喜欢以下列方式考虑inverse。我不知道这是否正式是它的工作方式,但它有助于我的世界有意义:

多到一个

many-to-one关系始终为inverse="false"。它们始终用于保持与数据库的关系。因为它们始终是inverse="false",所以不需要指定它,因此NHibernate(以及Fluent NHibernate)不提供它的选项。

(我只遇到过一种情况,我希望我可以在inverse="true"上指定many-to-one。如果您的一边有one-to-many list而另一方面many-to-one,您应该能够让list控制关系,以便NHibernate可以为您设置index值。就目前而言,您必须向子类添加属性并自行管理index值。)

一到一个

one-to-one关系始终为inverse="true"。如果没有idmany-to-one在关系的另一边,它们将永远存在,这将关注持久的关系。由于inverse值始终相同,因此无需指定它,因此不支持指定它。

集合

baglistset等集合可能会也可能不会成为双向关系的一部分。如果它们本身存在(也许是一包字符串元素),那么它们需要inverse="false"(这是默认值),因为没有其他人负责持久保持这种关系。但是,如果它们与另一种关系一起存在(如传统的一对多/多对一),则应将它们指定为inverse="true"

对于many-to-many个集合,您在关系的任意一侧有一个集合,请将其中一个标记为inverse="true",并将另一个标记保留为默认inverse="false"。同样,关键是关系的一方必须是非反向的。你应该选择哪一方?例如,如果我们在用户和角色之间采用多对多关系,那么您可能拥有大量用户和一些角色。在我看来,您应该将Role.Users映射为inverse="true"并让User.Roles控制关系,因为它是一个较小的数据集,并且它可能是您更关心的集合。< / p>

(事实上,我会犹豫是否将Role.Users包含在模型中。假设“客户”角色有100,000个用户。那么customerRole.Users是一个无法使用的延迟加载炸弹等待爆炸。)

...回到你的问题......

由于关系的哪一侧是反向的并不重要,只要一方是非反向的,那么你应该使one-to-one方反面,因为那是NHibernate想要的方式做到这一点。不要在无关紧要的事情上对抗工具。在您提供的映射中,关系的两侧基本上都标记为非反向,这导致关系持续两次。以下映射应该更适合您:

public class Parent
{
    public virtual Guid Id { get; set; }
    public virtual Child Child { get; set; }
}

public class ParentClassMap : ClassMap<Parent>
{
    public ParentClassMap()
    {
        Id(x => x.Id);
        HasOne(x => x.Child)
            .PropertyRef(x => x.Parent)
            .Cascade.All();
    }
}

public class Child
{
    public virtual Guid Id { get; set; }
    public virtual Parent Parent { get; set; }
}

public class ChildClassMap : ClassMap<Child>
{
    public ChildClassMap()
    {
        Id(x => x.Id);
        References(x => x.Parent)
            .Not.Nullable()
            .Unique()
            .Cascade.SaveUpdate();
    }
}

...从测试插入代码中产生以下SQL:

exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5'
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5',@p1='BE6D931A-8A05-4662-B5CD-9F36000797FF'

没有更新查询!