我正在尝试让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-941,NH-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文章推荐)。
答案 0 :(得分:31)
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"
。如果没有id
或many-to-one
在关系的另一边,它们将永远存在,这将关注持久的关系。由于inverse
值始终相同,因此无需指定它,因此不支持指定它。
bag
,list
,set
等集合可能会也可能不会成为双向关系的一部分。如果它们本身存在(也许是一包字符串元素),那么它们需要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'
没有更新查询!