C# NHibernate发出无关的update语句,而不考虑关系上的正确反向(流畅的NHibernate)设置
下面的类以最简单的方式表示我使用遗留数据库的真实场景。我可以向其中添加新列,但这是我唯一能做的,因为300+100表数据库被许多其他遗留应用程序使用,这些应用程序不会被移植到NHibernate(因此,从复合键迁移不是一个选项): 以下是为前面代码生成的SQL语句:C# NHibernate发出无关的update语句,而不考虑关系上的正确反向(流畅的NHibernate)设置,c#,nhibernate,fluent-nhibernate,C#,Nhibernate,Fluent Nhibernate,下面的类以最简单的方式表示我使用遗留数据库的真实场景。我可以向其中添加新列,但这是我唯一能做的,因为300+100表数据库被许多其他遗留应用程序使用,这些应用程序不会被移植到NHibernate(因此,从复合键迁移不是一个选项): 以下是为前面代码生成的SQL语句: -- statement #1 INSERT INTO [Parent] DEFAULT VALUES; select SCOPE_IDENTITY() -- statement #2 INSERT INTO [Child]
-- 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中的批插入中发送到数据库
如果父映射没有在HasMany(一对多)关系上设置Inverse属性,这将是预期的行为
事实上,当我们摆脱从孩子到物品的一对多关系时,它变得更加奇怪,就像这样:
从子项中删除集合并将子属性添加到项中:
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;
}
更改子项和项的映射,以从项中删除HasMany,并将项上的复合键上的引用添加回子项:
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"));
}
}
生成的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语句,但对象模型不是自然建模的,因为我不希望项有一个指向Child的链接,并且我需要Child中的项集合
我找不到任何方法来阻止那些不想要的/不需要的UPDATE语句,除了从Child中删除任何HasMany关系。看来,既然孩子已经是“倒转”一对多关系中的“多”(它负责拯救自己),当它是另一对多倒转关系中的“一”部分时,它就不尊重倒转设置
这让我快发疯了。如果没有经过深思熟虑的解释,我无法接受这些额外的更新语句:-)有人知道这里发生了什么吗?经过一整晚的努力,即使在堆栈溢出中也看不到答案:-)我想出了解决方案。。。我开始认为,可能是子对象中的更改被视为父对象集合中的更改,然后导致实体版本的更改。读了这篇文章后,我的猜测开始坚定: (13) 乐观锁(可选-默认为true):该 对集合状态的更改将导致 拥有实体的版本。(对于一对多协会而言,通常 禁用此设置是合理的。) (可在此处找到:) 然后,我天真地更改了父对象上的映射,使其不使用乐观锁,如下所示:
public MapeamentoParent()
{
Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity();
HasMany<Child>(_ => _.Children)
.Inverse()
.AsSet()
.Cascade.All()
.Not.OptimisticLock()
.KeyColumn("PARENT_ID");
}
我很幸运地注意到版本被更新为2!(离题:我使用的是DateTime版本字段,但由于它的精度不是无限的,所以当我开始认为这是一个版本控制问题时,我故意将其更改为一个完整的版本,这样我就可以看到版本中的每一个增量,并且不会错过在不到毫秒的时间内发生的增量,而这些增量是无法通过日期跟踪的。)时间版本,因为它的精度,或缺乏)。因此,在再次绝望之前,我已将父对象的HasMany更改为以前的状态(尝试隔离任何可能的解决方案),并将Not.OPTIMITICLOCK()添加到子对象的映射中(在所有似乎更新了版本的实体都是子对象之后!):
没有任何无关的更新语句!!!:-)强>
问题是我仍然无法解释为什么它以前不起作用。出于某种原因,当子对象与另一个实体具有一对多关系时,会执行无关的SQL语句。必须在子对象上的这些一对多集合上将乐观锁设置为false。我也不知道为什么所有子对象的版本都同时更改,仅仅因为子类与添加到其中的项有一对多的关系。当只有一个子对象被更改时,增加所有子对象的版本号是没有意义的
我最大的问题是为什么父对象集合中的所有子对象都在更新,即使我没有向任何子对象添加任何项。这仅仅是因为孩子和物品有很多关系。。。(无需向任何子级添加任何项即可“获取”这些额外更新)。在我看来,NHibernate在这里把事情搞错了,但由于我对NHibernate完全缺乏更深入的了解,我不能肯定,也不能准确指出问题所在,甚至不能肯定这确实是一个问题,因为这很可能是我完全缺乏NHibernate的摸索能力——真正的罪魁祸首!:-)
我希望有更开明的人来解释发生了什么事情,但是按照文档的建议,将一对多关系的乐观锁设置为false解决了这个问题。我发现parent.AddChild(…)和child.parent=parent之间存在差异;后者不会导致无关的版本更新(它不会在会话期间将子项添加到Parent.Children集合中,但在稍后的会话中加载父项时,它就在那里。)@BatteryBackupUnit这是真的,但作为ORM,它不应该“行为不检”将其添加到集合或直接设置父对象时,情况会有所不同。有时我们确实需要在从数据库加载它之前,立即将它放在集合中。在这些情况下,这些无关的疑问仍然存在。但很高兴知道,如果我们只想把孩子和父母“绑在一起”,我们就可以“阻止”他们。。。只需使用child.Parent,而不是添加到父集合中即可。:-)我完全同意。我已将FluentNHibernate
IHasManyConvention
与.Not.OptimisticLock()
添加到我们的约定中,以确保这是默认行为。我花了半个上午跟踪了这一点。对我来说奇怪的是
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"));
}
}
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();
}
-- 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 */)
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 */
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"));
}
}
-- 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 */