(更新见底部)
所以,我有两个对象(我们称它们为 ClassA 和 ClassB),它们表现出多对多的关系(ClassA 可以有多个 ClassB 对象,反之亦然)。
然而,与传统的多对多不同,实际的“关系”本身是一个对象,具有关于其他两个类之间的链接的不同信息(关系开始日期、关系名称等)
因此,我没有将其定义为 ClassA 和 ClassB 之间的多对多,而是定义了两个一对多关系(一个在 ClassA 上,一个在 ClassB 上)指向第三个中间类(我们称之为“关系”)。在结构上,我希望这些表看起来像这样:
ClassA Relationship
---------------- ----------------
Id (PK) Id (PK)
Name Name
Description StartDate
ClassA_Id (FK)
ClassB ClassB_Id (FK)
----------------
Id (PK)
Name
Description
为了实现这一点,我设置了这样的映射(经过大量试验和错误):
public ClassAMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Relationship)
.KeyColumn("ClassA_Id")
.ForeignKeyConstraintName("FK_Relationship_ClassA")
.LazyLoad()
.Cascade.All()
.Inverse();
}
public ClassBMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Relationship)
.KeyColumn("ClassB_Id")
.ForeignKeyConstraintName("FK_Relationship_ClassB")
.LazyLoad()
.Cascade.All()
.Inverse();
}
public RelationshipMap()
{
Id(x => x.Id);
Map(x => x.CreatedDate);
References(x => x.ClassAObject)
.Column("ClassA_Id")
.Cascade.SaveUpdate();
References(x => x.ClassBObject)
.Column("ClassB_Id")
.Cascade.SaveUpdate();
}
现在,对于添加/删除父对象,他的工作完全符合预期。例如,如果我创建一个新的 ClassA 和 ClassB,通过关系将它们链接起来,然后保存 ClassA,那么所有关联的记录都会添加到三个表中。同样,如果我删除相同的 ClassA,则 ClassA 记录和关系记录将被删除,但 ClassB 记录仍然存在(预期行为)。
但是,如果我只是尝试从其中一个父对象(例如classA.Relationships.Remove(relationshipObject); classARepository.Update(classA);
)中删除关系,则会收到类似于以下内容的错误:
NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [ClassB]
NHibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations) [Relationship#9]
此外,如果我完全从数据库中重新加载 ClassA 对象并如上所述删除成员资格,那么它会正确执行,但不会从数据库中删除我的关系。
如何更改映射以实现我正在寻找的行为?
作为参考,这里概述了必须通过的场景:
- 使用关系添加新的 ClassA
- 添加或更新 A/B 类/关系的记录
- 删除现有的 A 类
- 已删除 ClassA 和关系的记录
- ClassB 记录保留在表中
从 ClassA.Relationships 列表中删除关系;更新 A 类- 从关系表中删除关系
A 类和 B 类记录保留
更新:根据@JamieIde 的回答,我提出了以下调整:
- 取消关系上对 ClassA 和 ClassB 的引用:
// NOTE, this is an example snippet to show the steps that need to be taken
using(var session = SomeSessionFactory().OpenSession())
{
using (var transaction = session.BeginTransaction())
{
// preferably place these four lines in a public method on one of the objects
classAObject.Relationships.Remove(relationship);
classBObject.Relationships.Remove(relationship);
relationship.ClassA = null;
relationship.ClassB = null;
transaction.Commit();
}
}
.Cascade.All()
将 ClassA/ClassB更改为Cascade.AllDeleteOrphan()
. 这可以防止 NHibernate 在清空关系对象上的引用后将空白记录插入到 ClassA 和 ClassB 表中
public ClassAMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Relationship)
.KeyColumn("ClassA_Id")
.ForeignKeyConstraintName("FK_Relationship_ClassA")
.LazyLoad()
.Cascade.AllDeleteOrphan() // here's the key line
.Inverse();
}