以下类以极少的方式表示我的遗留数据库的真实场景。我可以添加新列,但这是我能做的全部,因为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已经是“倒”一对多关系中的“多个”(它本身就是保存),所以当它是另一个的“一个”部分时,它不尊重逆向设置。多对多关系...
这让我疯了。我不能接受那些额外的UPDATE语句而没有任何好的解释:-)是否有人知道这里发生了什么?
答案 0 :(得分:10)
在彻夜难眠之后,即使在Stack Overflow中也没有希望得到答案:-)我已经想出了解决方案......我开始认为这可能是对孩子的改变了被视为父集合中的更改的对象,然后导致对实体版本的更改。读完之后我的猜测开始凝固了:
(13)optimistic-lock(可选 - 默认为true):物种 对集合状态的更改会导致集合的增量 拥有实体的版本。 (对于一对多的协会,它经常是 合理地禁用此设置。) (见http://nhibernate.info/doc/nh/en/index.html#collections)
然后我天真地改变了Parent上的映射,不使用乐观锁,如下所示:
public MapeamentoParent()
{
Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity();
HasMany<Child>(_ => _.Children)
.Inverse()
.AsSet()
.Cascade.All()
.Not.OptimisticLock()
.KeyColumn("PARENT_ID");
}
这不起作用。但后来我在无关的更新中注意到了一些有趣的事情:
-- statement #1
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 1 /* @p2 */
AND version = 1 /* @p3 */
-- statement #2
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 2 /* @p2 */
AND version = 1 /* @p3 */
-- statement #3
UPDATE [Child]
SET version = 2 /* @p0 */
WHERE PARENT_ID = 1 /* @p1 */
AND CHILD_ID = 3 /* @p2 */
AND version = 1 /* @p3 */
我很幸运地注意到该版本已更新为2! (题外话:我使用的是DateTime版本字段,但由于它没有无限的精度,当我开始认为它是一个版本问题时,我故意将其改为完整版本,以便我可以看到版本中的每一个增量,并且不要错过在不到几毫秒内发生的增量,由于它的精度或缺乏,它们无法由DateTime版本追踪。所以,在再次绝望之前,我已经将Parent的HasMany改回原来的状态(尝试隔离任何可能的解决方案)并将Not.OptimisticLock()添加到Child的地图中(在所有似乎是将他们的版本更新为儿童!):
public class MapeamentoChild : ClassMap<Child>
{
public MapeamentoChild()
{
CompositeId()
.KeyReference(_ => _.Parent, "PARENT_ID")
.KeyProperty(_ => _.ChildId, "CHILD_ID");
HasMany(_ => _.Items)
.AsSet()
.Cascade.All()
.Not.OptimisticLock()
.KeyColumns.Add("PARENT_ID")
.KeyColumns.Add("CHILD_ID");
Version(Reveal.Member<Child>("version"));
}
}
它完美地发布了以下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 [Item]
SET PARENT_ID = 1 /* @p0_0 */,
CHILD_ID = 1 /* @p1_0 */
WHERE ItemId = 1 /* @p2_0 */
没有任何特别的更新声明! : - )强>
问题是我仍然无法解释为什么以前没有用。出于某种原因,当Child与另一个实体有一对多关系时,将执行无关的SQL语句。您必须在Child对象上的这些一对多集合上将乐观锁设置为false。我不知道为什么所有的Child对象都同时更改了它们的版本,只是因为Child类与添加到它的Item有一对多的关系。当只改变其中一个对象时,增加所有Child对象的版本号是没有意义的!
我最大的问题是,即使我没有向任何Child对象添加任何Item,为什么Parent的集合上的所有Child对象都被更新了。仅发生这样的事实,Child与Item具有HasMany关系...(无需向任何Child添加任何项目以“获取”这些额外更新)。在我看来,NHibernate在这里错误地搞清楚了事情,但由于我完全没有对NHibernate有任何更深入的了解,我无法肯定地说,也没有确切地指出问题的确切位置,甚至没有明确地确认它确实是一个问题。它可能是我完全缺乏NHibernate grokking力量的真正罪魁祸首! : - )
我希望有更开明的人来解释正在/正在发生的事情,但是如文档所建议的那样,在一对多关系中将乐观锁设置为假可以解决问题。