我有一些实体(为简单起见,它看起来像这样):
class DbEntity
{
public Guid Id {get;set;}
}
class BaseEntity: DbEntity
{
public string Name {get;set;}
public ParentEntity Parent {get;set;}
}
class ParentEntity: BaseEntity
{
List<BaseEntity> Children {get;set;}
}
class ChildEntity: BaseEntity
{
}
现在,我的流利映射看起来像这样:
class DbEntityMap<T>: ClassMap<T> where T: DbEntity
{
public DbEntityMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
}
}
class BaseEntityMap: DbEntityMap<BaseEntity>
{
UseUnionSubclassForInheritanceMapping();
Map(x => x.Name).Not.Nullable();
References(x => x.Parent).Cascade.None();
}
class ParentEntityMap: SubclassMap<ParentEntity>
{
Abstract();
HasMany(x => x.Children)
.ForeignKeyCascadeOnDelete()
.Inverse()
.Not.KeyUpdate()
.Cascade.None()
.LazyLoad();
}
class ChildEntityMap: SubclassMap<ChildEntity>
{
Abstract();
//some other stuff
}
现在,我有将请求发送到WebAPI的客户端桌面应用程序。 第一个请求是创建所有实体,例如:
ParentEntity parent = new ParentEntity();
parent.Name = "New name";
ChildEntity child1 = new ChildEntity();
ChildEntity child2 = new ChildEntity();
parent.Children.Add(child1);
parent.Children.Add(child2);
//of course children know their parent:
//child.Parent = parent;
现在,我正在从中构建一些dto(因为我的模型要复杂得多,并且创建dto是个好主意)来构建json,并将此json发送到WebAPI。数据库中的实体已正确创建。很好。
但是现在我必须修改一些子实体,例如:
child1.SomeValue = newValue;
现在,在创建DTO时,我不会向其添加父实体,只需添加父ID,它看起来就更少了:
class ChildDto
{
public Guid DbId {get;set;}
public Guid ParentDbId {get;set;}
public int SomeValue {get;set;}
}
现在,当我的WebAPI收到此类dto时,它会重新创建一个像这样的模型:
ParentEntity fakeParent = new ParentEntity();
fakeParent.Id = childDto.ParentDbId;
ChildEntity child = new ChildEntity();
//assign other child values and then parent:
child.Parent = fakeParent;
现在,当我执行Update(只是session.Update(obj,id))时,我的子实体会正确更新,但父实体也会更新。由于我没有在父实体中设置Name属性,因此数据库中的Name字段变为空。
我认为将Cascade设置为None将阻止NHibernate更新父实体。但是没有级联设置有效。如果我将父绑定设置为:
References(x => x.Parent).ReadOnly()
然后我的ChildEntity会更新而没有父级ID。
我至少知道几种解决方案,例如: 1.在DTO中也转换整个父实体-但这可以进行其他级联更新,如果没有,则将进行两次更新而不是一次。 2.在更新之前选择父实体-但这会创建不必要的SELECT。
我想要实现的是: -仅更新childEntity-无需更新父级或任何其他实体。
我该如何完成呢?
[关于实际应用的一些话]
实际上,我的ParentEntity可以容纳BaseEntities。 ParentEntity和ChildEntity也具有一些共同的属性。
此外,ChildEntity可以容纳其他DbEntities。 子实体的子代还拥有其他DbEntities,如下所示:
ParentEntity-> ChildEntity-> EntityA-> EntityB-> EntityC (就像一棵树)
(每个派生自DbEntity)。 而且我什至设法使用session.Merge解决了问题,但是当我尝试更新EntityA时,问题还是一样的-ChildEntity与ParentEntity失去连接:|
答案 0 :(得分:1)
我认为将Cascade设置为None将阻止NHibernate更新父实体。但是级联设置无效
您期望Cascade.None
将阻止更新关联。尽管这是事实,但您的代码中并非如此。由于您的 strange 继承层次结构,父实体正在更新。我不了解需要多级继承。如果您删除了BaseEntity
,那么您将无需致电Load
就可以实现想要实现的目标。
here说明了您正在使用的方案:
10.4.2。更新分离的对象
许多应用程序需要在一个事务中检索对象,将其发送到UI层进行操作,然后将更改保存在新事务中。 ........
这种方法需要与上一节中描述的编程模型稍有不同的编程模型。 NHibernate通过提供方法ISession.Update()支持此模型。
您还应该查看this:
10.10。生命周期和对象图
级联操作的精确语义如下:
- 如果保存了父项,则所有子项都将传递到SaveOrUpdate()
- 如果将父级传递给Update()或SaveOrUpdate(),则所有子级都传递给SaveOrUpdate()
- 如果持久子级引用了临时子级,则将其传递给SaveOrUpdate()
- 如果删除了父级,则所有子级都会传递给Delete()
- 如果持久子级取消了临时子级的引用,则不会发生任何特殊情况(应用程序应在必要时显式删除该子级),除非cascade =“ all-delete-orphan”或cascade =“ delete-orphan”,在这种情况下“孤儿”被删除。
注意上面突出显示的点。您的ParentEntity
和ChildEntity
均来自BaseEntity
。此外,BaseEntity
拥有关联属性ParentEntity Parent
。当您Update
子实体时,您的父实体将传递给SaveOrUpdate
。由于您尚未在此处指定Name
,因此它将使用空名称更新数据库。
如果我将父绑定设置为:
References(x => x.Parent).ReadOnly()
然后我的ChildEntity会更新而没有父级ID。
这很明显;您已将其明确设置为只读。
答案 1 :(得分:0)
您应该session.Load
不想更新的现有对象(不要将其与session.Get
混淆-它不会为惰性实体发出SELECT语句)。
因此,您需要将DTO->实体转换逻辑更改为以下内容:
ParentEntity fakeParent = session.Load<ParentEntity>(childDto.ParentDbId);
ChildEntity child = new ChildEntity();
//assign other child values and then parent:
child.Parent = fakeParent;