以下类以最低限度的方式代表了我使用遗留数据库的真实场景。我可以向它添加新列,但这就是我所能做的,因为 300+ 百个表数据库被许多其他不会移植到 NHibernate 的遗留应用程序使用(所以从复合键迁移不是一个选项) :
public class Parent
{
public virtual long Id { get; protected set; }
ICollection<Child> children = new HashSet<Child>();
public virtual IEnumerable<Child> Children { get { return children; } }
public virtual void AddChildren(params Child[] children)
{
foreach (var child in children) AddChild(child);
}
public virtual Child AddChild(Child child)
{
child.Parent = this;
children.Add(child);
return child;
}
}
public class Child
{
public virtual Parent Parent { get; set; }
public virtual int ChildId { get; set; }
ICollection<Item> items = new HashSet<Item>();
public virtual ICollection<Item> Items { get { return items; } }
long version;
public override int GetHashCode()
{
return ChildId.GetHashCode() ^ (Parent != null ? Parent.Id.GetHashCode() : 0.GetHashCode());
}
public override bool Equals(object obj)
{
var c = obj as Child;
if (ReferenceEquals(c, null))
return false;
return ChildId == c.ChildId && Parent.Id == c.Parent.Id;
}
}
public class Item
{
public virtual long ItemId { get; set; }
long version;
}
这就是我将这些映射到“现有”数据库的方式:
public class MapeamentoParent : ClassMap<Parent>
{
public MapeamentoParent()
{
Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity();
HasMany(_ => _.Children)
.Inverse()
.AsSet()
.Cascade.All()
.KeyColumn("PARENT_ID");
}
}
public class MapeamentoChild : ClassMap<Child>
{
public MapeamentoChild()
{
CompositeId()
.KeyReference(_ => _.Parent, "PARENT_ID")
.KeyProperty(_ => _.ChildId, "CHILD_ID");
HasMany(_ => _.Items)
.AsSet()
.Cascade.All()
.KeyColumns.Add("PARENT_ID")
.KeyColumns.Add("CHILD_ID");
Version(Reveal.Member<Child>("version"));
}
}
public class MapeamentoItem : ClassMap<Item>
{
public MapeamentoItem()
{
Id(_ => _.ItemId).GeneratedBy.Assigned();
Version(Reveal.Member<Item>("version"));
}
}
这是我用来插入具有三个孩子和一个孩子的父项的代码:
using (var tx = session.BeginTransaction())
{
var parent = new Parent();
var child = new Child() { ChildId = 1, };
parent.AddChildren(
child,
new Child() { ChildId = 2, },
new Child() { ChildId = 3 });
child.Items.Add(new Item() { ItemId = 1 });
session.Save(parent);
tx.Commit();
}
这些是为前面的代码生成的 SQL 语句:
-- statement #1
INSERT INTO [Parent]
DEFAULT VALUES;
select SCOPE_IDENTITY()
-- statement #2
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_0 */,
1 /* @p1_0 */,
1 /* @p2_0 */)
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_1 */,
1 /* @p1_1 */,
2 /* @p2_1 */)
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_2 */,
1 /* @p1_2 */,
3 /* @p2_2 */)
-- statement #3
INSERT INTO [Item]
(version,
ItemId)
VALUES (1 /* @p0_0 */,
1 /* @p1_0 */)
-- statement #4
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 1 /* @p2 */
AND version = 1 /* @p3 */
-- statement #5
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 2 /* @p2 */
AND version = 1 /* @p3 */
-- statement #6
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 3 /* @p2 */
AND version = 1 /* @p3 */
-- statement #7
UPDATE [Item]
SET PARENT_ID = 1 /* @p0_0 */,
CHILD_ID = 1 /* @p1_0 */
WHERE ItemId = 1 /* @p2_0 */
语句 4、5 和 6 是多余的/多余的,因为所有这些信息已经在语句 2 中以批量插入的形式发送到数据库。
如果 Parent 映射没有在 HasMany(一对多)关系上设置 Inverse 属性,这将是预期的行为。
事实上,当我们像这样摆脱从 Child 到 Item 的一对多关系时,它变得更奇怪了:
从 Child 中删除集合并将 Child 属性添加到 Item:
public class Child
{
public virtual Parent Parent { get; set; }
public virtual int ChildId { get; set; }
long version;
public override int GetHashCode()
{
return ChildId.GetHashCode() ^ (Parent != null ? Parent.Id.GetHashCode() : 0.GetHashCode());
}
public override bool Equals(object obj)
{
var c = obj as Child;
if (ReferenceEquals(c, null))
return false;
return ChildId == c.ChildId && Parent.Id == c.Parent.Id;
}
}
public class Item
{
public virtual Child Child { get; set; }
public virtual long ItemId { get; set; }
long version;
}
更改 Child 和 Item 的映射以从 Item 中删除 HasMany 并将 Item 上的复合键上的 References 添加回 Child:
public class MapeamentoChild : ClassMap<Child>
{
public MapeamentoChild()
{
CompositeId()
.KeyReference(_ => _.Parent, "PARENT_ID")
.KeyProperty(_ => _.ChildId, "CHILD_ID");
Version(Reveal.Member<Child>("version"));
}
}
public class MapeamentoItem : ClassMap<Item>
{
public MapeamentoItem()
{
Id(_ => _.ItemId).GeneratedBy.Assigned();
References(_ => _.Child).Columns("PARENT_ID", "CHILD_ID");
Version(Reveal.Member<Item>("version"));
}
}
将代码更改为以下内容(注意现在我们需要显式调用 save Item):
using (var tx = session.BeginTransaction())
{
var parent = new Parent();
var child = new Child() { ChildId = 1, };
parent.AddChildren(
child,
new Child() { ChildId = 2, },
new Child() { ChildId = 3 });
var item = new Item() { ItemId = 1, Child = child };
session.Save(parent);
session.Save(item);
tx.Commit();
}
生成的 sql 语句为:
-- statement #1
INSERT INTO [Parent]
DEFAULT VALUES;
select SCOPE_IDENTITY()
-- statement #2
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_0 */,
1 /* @p1_0 */,
1 /* @p2_0 */)
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_1 */,
1 /* @p1_1 */,
2 /* @p2_1 */)
INSERT INTO [Child]
(version,
PARENT_ID,
CHILD_ID)
VALUES (1 /* @p0_2 */,
1 /* @p1_2 */,
3 /* @p2_2 */)
-- statement #3
INSERT INTO [Item]
(version,
PARENT_ID,
CHILD_ID,
ItemId)
VALUES (1 /* @p0_0 */,
1 /* @p1_0 */,
1 /* @p2_0 */,
1 /* @p3_0 */)
如您所见,没有多余/多余的 UPDATE 语句,但对象模型不是自然建模的,因为我不希望 Item 具有返回 Child 的链接,并且我需要 Child 中的 Items 集合。
除了从 Child 中删除任何 HasMany 关系外,我找不到任何方法来阻止那些不需要/不需要的 UPDATE 语句。似乎由于 Child 已经是“倒置”一对多关系中的“多”(它负责保存自己),因此当它是另一个“一个”部分时,它不尊重 Inverse 设置对多倒置关系...
这让我发疯了。如果没有任何深思熟虑的解释,我不能接受那些额外的 UPDATE 语句 :-) 有人知道这里发生了什么吗?