【问题标题】:NHibernate Cascade.None still updates parent entityNHibernate Cascade.None 仍然更新父实体
【发布时间】:2019-08-12 15:54:20
【问题描述】:

我有一些实体(为简单起见,看起来像这样):

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(just 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,如下所示:

父实体->子实体->实体A->实体B->实体C (像一棵树)

(每个都派生自 DbEntity)。 我什至设法使用 session.Merge 解决了这个问题,但是当我尝试更新例如 EntityA 时,问题是一样的 - ChildEntity 失去与 ParentEntity 的连接:|

【问题讨论】:

    标签: c# nhibernate


    【解决方案1】:

    我认为将 Cascade 设置为 None 会阻止 NHibernate 更新父实体。但没有 Cascade 设置有效

    您预计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",在这种情况下“孤儿”被删除。

    请注意上面突出显示的点。您的ParentEntityChildEntity 都派生自BaseEntity。此外,BaseEntity 拥有关联属性ParentEntity Parent。当您 Update 子实体时,您的父实体将传递给 SaveOrUpdate。由于您没有在此处指定Name,它会使用空名称更新数据库。

    如果我将父绑定设置为:

    References(x => x.Parent).ReadOnly()
    

    然后我的 ChildEntity 会在没有父 ID 的情况下更新。

    这很明显;您明确将其设置为只读。

    【讨论】:

    • 我需要这样的继承,因为在我的真实代码中 ParentEntity 有 BaseEntity 的列表。 ParentEntity 和 ChildEntity 有一些共同的属性。当我尝试在没有子类化的情况下进行映射时,我收到一条错误消息,指出 BaseEntity 未映射。所以我有点卡住了。
    • @AdamJachocki:我说继承很奇怪,因为ParentEntity 也拥有它自己的关联属性。您可以绕过BaseEntity 将其成员转移到ParentEntity 并从ParentEntity 派生ChildEntity。但;我不太了解您的实际应用;这就是为什么我要求你这样做只是为了测试。
    【解决方案2】:

    您应该 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;
    

    【讨论】:

    • 但这不仅需要不必要的选择,而且在我的情况下也是相当戏剧化的。因为我做了这样的事情:foreach(var item in itemsToModify) session.Update(item)。而且我真的不知道我要推送到数据库的对象类型。我需要先检查类型,这真的很戏剧化。
    • 我还有很多实体。他们中的一些人没有任何父母或有来自不同表的父母。所以实现这样的东西真的很难而且效率很低。
    • 所以你建议我应该调用 Load 然后更新而不是更新?
    • @AdamJachocki 您有一些将childDto.ParentDbId 转换为fakeParent 实体对象的代码。我说这个转换应该看起来像session.Load&lt;ParentType&gt;(parentId),这个结果应该分配给childEntity.ParentProperty。那么,是什么让您可以接受创建仅指定 Id 的假 ParentType 而调用 session.Loade&lt;ParentType&gt;(parentId) 而不是不可接受的?
    • 这是在两个不同的应用层中完成的。在将实体保存在数据库中时,我根本不知道它的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 2013-07-24
    相关资源
    最近更新 更多