理解外键not null=与NHibernate的零对一关系中的正确和相反行为

理解外键not null=与NHibernate的零对一关系中的正确和相反行为,nhibernate,nhibernate-mapping,Nhibernate,Nhibernate Mapping,我试图让NHibernate使用集合的多个方面来管理双向关联,以模拟零对一关系 父类和映射: public class Parent { private ICollection<Child> children; public Parent() { this.children = new HashedSet<Child>(); } public virtual Guid Id { get; protected inte

我试图让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();
    }
}
请注意,对于第二次插入和以下更新,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属性时,会引用null或瞬时值反转

var parent = new Parent();
var child = new Child();
parent.Child = child;
//child.Parent = parent;
session.Save(parent);
session.Flush();
我已经找到了很多关于这方面的帖子,但是还没有找到一个关于如何做零对一的明确指南,在
one
侧有
inverse=false

我试过上面提到的一对多/一对一的方法

此外,我在NHibernate上发现了几个关于(不)可空外键的开放问题:,等等

我做错了什么

编辑2011-05-30

因此,我的临时解决方案是在多方面使用标准的
inverse=true
设置,并在父级的setter中使用一些魔法:

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不允许manytonepart像文章建议的那样设置
inverse=true

何时使用inverse=“true | false”
inverse
属性用于帮助NHibernate知道应该使用关系的哪一侧来保持关系。非反向侧(请注意双负)是将被持久化的侧。如果任何一方都不是相反的,那么关系将保持两次,就像前面提供的
INSERT
之后紧接着是
UPDATE
示例一样。如果双方都是反向的,那么关系就根本不会持久,因此正确设置反向是很重要的

我喜欢用下面的方式来思考
。我不知道这是否是正式的工作方式,但它帮助我的世界变得有意义:

多对一
多对一
关系总是
inverse=“false”
。它们始终用于保持与数据库的关系。因为它们总是
inverse=“false”
,所以不需要指定它,所以NHibernate(因此Fluent NHibernate)不提供选项

(我只遇到过一种情况,希望能够指定
inverse=“true”
多对一
上的
。如果一方是
一对多
列表,另一方是
多对一
,您应该能够让
列表
控制关系,以便NHibernate可以为您设置
索引
值。目前,您有向子类添加属性并自己管理
索引
值。)

一对一
一对一
关系始终是
inverse=“true”
。在关系的另一端,如果没有
id
多对一
,它们就永远不会存在,这将负责保持关系。由于
值始终相同,因此不需要指定它,因此不支持指定它

收藏 像
bag
list
set
等集合可能是也可能不是双向关系的一部分。如果它们单独存在(可能是一包字符串元素),那么它们需要是
inverse=“false”
(这是默认值),因为没有其他人负责保持关系。但是,如果它们与其他关系同时存在(就像传统的一对多/多对一),则应将它们指定为
inverse=“true”

使用“多对多”
集合,在关系的任意一侧都有一个集合,将其中一个标记为
reverse=“true”
,将另一个保留为默认值
reverse=“false”
。同样,关键是关系的一侧必须是非反向的。你应该选择哪一边?如果我们以用户和角色之间的多对多关系为例,您可能有很多用户和一些角色。在我看来,您应该将
Role.Users
映射为
inverse=“true”
,并让
User.Roles
控制关系,因为它是一组较小的数据,而且可能是您更关心的集合

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

…回到你的问题上。。。 因为关系的哪一方是相反的并不重要,只要其中一方是非相反的,那么你就应该让
一对一
的一方成为相反的一方,因为这是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();
    }
}

没有更新查询

相关答案(由我:-)如果您有一个中间表,其中包含两个实体之间的关系,那么多对多呢?@Dark_Knight
多对多
在上面的“集合”部分的第2段中介绍。
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();
    }
}
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'