无论关系中是否存在正确的Inverse(流畅的nhibernate)设置,NHibernate都会发出无关的更新语句

以下类以极少的方式表示我的遗留数据库的真实场景。 我可以添加新列,但这是我能做的全部,因为许多其他遗留应用程序使用了300多个表数据库,这些应用程序不会移植到NHibernate(因此无法从复合键迁移) :

public class Parent { public virtual long Id { get; protected set; } ICollection children = new HashSet(); public virtual IEnumerable 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 items = new HashSet(); public virtual ICollection 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 { public MapeamentoParent() { Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity(); HasMany(_ => _.Children) .Inverse() .AsSet() .Cascade.All() .KeyColumn("PARENT_ID"); } } public class MapeamentoChild : ClassMap { 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("version")); } } public class MapeamentoItem : ClassMap { public MapeamentoItem() { Id(_ => _.ItemId).GeneratedBy.Assigned(); Version(Reveal.Member("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 { public MapeamentoChild() { CompositeId() .KeyReference(_ => _.Parent, "PARENT_ID") .KeyProperty(_ => _.ChildId, "CHILD_ID"); Version(Reveal.Member("version")); } } public class MapeamentoItem : ClassMap { public MapeamentoItem() { Id(_ => _.ItemId).GeneratedBy.Assigned(); References(_ => _.Child).Columns("PARENT_ID", "CHILD_ID"); Version(Reveal.Member("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语句而没有任何好的解释:-)是否有人知道这里发生了什么?

在彻夜难眠之后,即使在Stack Overflow中也没有希望得到答案:-)我已经提出了解决方案……我开始认为这可能是Child对象中的一个变化被视为父母集合中的更改,然后导致实体版本的更改。 读完之后,我的猜测开始凝固了:

(13)optimistic-lock(可选 – 默认为true):更改为集合状态的物种会导致拥有实体版本的增量。 (对于一对多关联,禁用此设置通常是合理的。)(在此处找到: http : //nhibernate.info/doc/nh/en/index.html#collections )

然后我天真地改变了Parent上的映射,不使用乐观锁,如下所示:

  public MapeamentoParent() { Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity(); HasMany(_ => _.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 { 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("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力量的真正罪魁祸首! 🙂

我希望有更开明的人来解释正在/正在发生的事情,但是如文档所建议的那样,在一对多关系中将乐观锁设置为假,解决了问题。