【问题标题】:NHibernate exception while deleting object graph: not-null property references a null or transient value删除对象图时出现 NHibernate 异常:非空属性引用空值或瞬态值
【发布时间】:2011-07-08 06:12:03
【问题描述】:

我有一个方案(字段不是必需的):

a busy cat http://picsearch.ru/share/image-BCE8_4E168F3B.jpg

我有映射:

实体

<class name="LogicalModel.Entity" table="`Entity`" lazy="true">
  <id name="Id" ..> ... </id>
  <bag name="Attributes" lazy="true" cascade="all-delete-orphan" fetch="select" batch-size="1" access="property" inverse="true">
    <key column="`Entity`" />
    <one-to-many class="LogicalModel.Attribute" />
  </bag>
  <bag name="Keys" lazy="true" cascade="all-delete-orphan" fetch="select" batch-size="1" access="property" inverse="true">
    <key column="`Entity`" />
    <one-to-many class="LogicalModel.Key" />
  </bag>
</class>

属性

<class name="LogicalModel.Attribute" table="`Attribute`" lazy="true">
  <id name="Id" ..> ... </id>
  <many-to-one name="Type" class="LogicalModel.Entity" column="`Type`" cascade="save-update" fetch="select" not-null="true" foreign-key="fk_TypeAttribute" />
  <many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityAttributes" />
</class>

<class name="LogicalModel.Key" table="`Key`" lazy="true">
  <id name="Id" ..> ... </id>
  <bag name="KeyAttributes" lazy="true" cascade="all-delete-orphan" fetch="select" access="property" inverse="true">
    <key column="`Key`" />
    <one-to-many class="LogicalModel.KeyAttribute" />
  </bag>
  <many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityKeys" />
</class>

关键属性:

<class name="LogicalModel.KeyAttribute" table="`KeyAttribute`" lazy="false">
   <id name="Id" ..> ... </id>
   <many-to-one name="Attribute" class="LogicalModel.Attribute" column="`Attribute`" cascade="save-update" fetch="select" not-null="true" foreign-key="fk_AttributeKeyAttribute" />
   <many-to-one name="Key" class="LogicalModel.Key" column="`Key`" cascade="none" fetch="select" not-null="true" foreign-key="fk_KeyKeyAttributes" />
</class>

现在请看... 如您所见,我们有单向主关联KeyAttribute - Attribute,所以它只是多对一,我根本不需要反向关联。

现在的问题是当我尝试删除 整个图 - 删除实体对象(注意:实体实际上根本没有加载,它只是一组代理,这就是 NHibernate 进行附加的原因SELECT 查询以在删除前检查引用) 像这样

Session.Delete(Entity);  //  here PropertyValueException: 
//  not-null property references a null or transient value:  LogicalModel.KeyAttribute.Attribute

Session.Flush();  // Actually I use transactions in my code, but don't mind

SQL 探查器:

exec sp_executesql N'SELECT entities0_.[Id] as Id1_1_, entities0_.[Id] as Id1_45_0_, 
FROM [Entity] entities0_ WHERE entities0_.[LogicalModel]=@p0',N'@p0 uniqueidentifier',@p0='DC8F8460-9C41-438A-8334-97D0A94E2528'

exec sp_executesql N'SELECT attributes0_.[Entity] as Entity12_1_, attributes0_.[Id] as Id1_1_, attributes0_.[Id] as Id1_16_0_, attributes0_.[Type] as Type11_16_0_, attributes0_.[Entity] as Entity12_16_0_ 
FROM [Attribute] attributes0_ WHERE attributes0_.[Entity]=@p0',N'@p0 uniqueidentifier',@p0='63E4D568-EAB2-4DF2-8FED-014C8CB2DE22'

exec sp_executesql N'SELECT keys0_.[Entity] as Entity4_1_, keys0_.[Id] as Id1_1_, keys0_.[Id] as Id1_43_0_, keys0_.[Entity] as Entity4_43_0_ 
FROM [Key] keys0_ WHERE keys0_.[Entity]=@p0',N'@p0 uniqueidentifier',@p0='63E4D568-EAB2-4DF2-8FED-014C8CB2DE22'

exec sp_executesql N'SELECT keyattribu0_.[Key] as Key4_1_, keyattribu0_.[Id] as Id1_1_, keyattribu0_.[Id] as Id1_0_0_, keyattribu0_.[Attribute] as Attribute3_0_0_, keyattribu0_.[Key] as Key4_0_0_ 
FROM [KeyAttribute] keyattribu0_ WHERE keyattribu0_.[Key]=@p0',N'@p0 uniqueidentifier',@p0='103D8FB3-0B17-4F51-8AEF-9623616AE282'

所以我们可以看到:

非空属性引用空值或瞬态值:LogicalModel.KeyAttribute.Attribute 发生在 KeyAttribute 类中的 NH 检查字段 Attribute (db 中的非空约束,没关系)之后(参见分析器日志)。

这很有趣,因为 NH 必须同时删除 Attributes 和 KeyAttributes,NH 在 KeyAttribute 类中读取有关 Attribute 字段的信息,FOUND 在 DB 中,NOT FOUNDNH session (!!!)(因为之前加载了属性),然后抛出这个愚蠢的错误。

我已经尝试过的事情: 1. 使 not-null="false"。在这种情况下,NH 会进行额外的更新 - 尝试设置 Attribute=NULL - 导致 DB 中的约束违规。 2. 在 KeyAttribute-Attribute 的多对一关联上设置lazy="false",lazy="no-proxy" - 无;

现在我不喜欢拦截器的想法,因为在很多情况下我都有相同的情况,我需要通用解决方案

各位,有什么建议吗?

【问题讨论】:

    标签: nhibernate cascade


    【解决方案1】:

    在我看来,这可能是由于您对模型的所有实体的延迟加载造成的。 删除实体时,它加载和删除引用的属性列表,加载引用的键列表,加载引用的 KeyAttribute 列表(具有删除键)然后它属于非空属性引用空值或瞬态值,因为之前已删除引用的属性在会话中。

    您可以通过删除映射文件中的所有延迟加载来检查。

    一个快速的解决方案可能是保持延迟加载,但在删除时强制完全加载模型(使用休眠初始化()),例如在实体工厂中的 Delete(Entity) 静态方法中。

    【讨论】:

    • 感谢您的回答。我试图加载完整的图表并在属性关闭时杀死惰性以使其非瞬态(我只是通过迭代它们来读取所有集合和链接:)),它没有帮助我((“..referenced Attribute has been之前在会话中删除..”这可能是!
    • @Vince:我认为这与延迟加载无关。
    • @Stefan Steinegger, @Vince 我去了反射器,似乎 NH 需要外部删除命令控制,因为在属性 persistContext 中没有关于与 KeyAttribute 的关联的信息。
    【解决方案2】:

    您是否尝试过在

    中设置 on-delete="cascade"
    <class name="LogicalModel.Key" table="`Key`" lazy="true">
    <id name="Id" ..> ... </id>
    <bag name="KeyAttributes" lazy="true" cascade="all-delete-orphan" fetch="select" access="property" inverse="true">
      <key column="`Key`" on-delete="cascade" />
      <one-to-many class="LogicalModel.KeyAttribute" />
    </bag>
    <many-to-one name="Entity" class="LogicalModel.Entity" column="`Entity`" cascade="none" fetch="select" not-null="true" foreign-key="fk_EntityKeys" />
    

    因为在配置文件中,您会看到 nh 尝试将某些内容更新为不可为空的 null

    【讨论】:

    • 嗯..我现在试过了,没有用,因为NH仍然不知道必须与KeyAttribute一起删除Attribute,但首先是KeyAttributes,没有检查Nullability。最后 NH 试图更新指向属性的链接,该链接不为空。如果我们通过反射器看到,问题出在方法 DeleteEntity -> 调用方法 NHibernate.Engine.Nullability.CheckNullability: public void CheckNullability(object[] values, IEntityPersister persister, bool isUpdate) 可能需要检查一个
    【解决方案3】:

    NH 有时需要将引用设置为空。通常这是为了避免存在循环引用的模型中出现问题。但找到一种方法来避免它并不总是足够聪明,即使是这样。

    因此可能需要在某些外键字段中允许空值,当然不仅在映射文件中,在数据库中也是如此。它实际上应该解决问题。


    或者,您也可以使用 HQL 逐表删除数据。这在您没有继承权并且知道所有实体以及删除它们的顺序的所有情况下都可以正常工作:

    object entityId;
    
    // gets keys to delete
    List<object> keyIds = Session
      .CreateQuery("select id from Key where Entity = :entity")
      .SetEntity("entity", Entity)
      .List<object>();
    
    // delete KeyAttribute which reference the key
    Session.CreateQuery("delete KeyAttribute where Key.id in (:keyIds)")
      .SetParameterList("keyIds", keyIds)
      .ExecuteUpdate();
    
    // delete the keys
    Session.CreateQuery("delete Key where id in (:keyIds)")   
      .SetParameterList("keyIds", keyIds)
      .ExecuteUpdate();
    
    // get attributes to delete
    List<object> attributeIds = Session
      .CreateQuery("select id from Attribute where Entity = :entity")
      .SetEntity("entity", Entity)
      .List<object>();
    
    // delete KeyAttributes which reference the attributes
    Session.CreateQuery("delete KeyAttribute where Attribute.id in (:attributeIds)")
      .SetParameterList("attributeIds", attributeIds )
      .ExecuteUpdate();
    
    // delete the attributes
    Session.CreateQuery("delete Attribute where id in (:attributeIds)")   
      .SetParameterList("attributeIds", attributeIds )
      .ExecuteUpdate();
    
    Session.CreateQuery("delete Entity where id = :entityId")   
      .SetParameter("entityId", Entity.Id)
      .ExecuteUpdate();
    

    注意:

    • 如果参数列表的大小超过 2000 左右(在 SQL Server 中),您可以将它们分解成小块。
    • 直接在数据库中删除时会话不同步。当您只做删除时,这不会导致任何问题。当你在同一会话中做其他工作人员时,删除后清除会话。

    【讨论】:

    • 感谢您的回答。我认为这是相当难的编码,尤其是在 HQL 中!现在我正在尝试在自己的 DeleteEventListener 中覆盖 CascadeBeforeDelete,我想我可以通过这种方式设置正确的删除顺序
    • @EvgeniyK:等一下,我说允许空值应该解决问题。 “硬编码”部分只是为您提供更多控制权,可能是一个很好的性能优化。
    • 是的,如果我放弃 NH 和 MS SQL 中的所有约束,当然一切都会好起来的,毫无疑问;在俄罗斯,它称之为:“违反体育道德的行为”=))
    • @EvgeniyK:我认为这是使用 ORM 的一种权衡。您不需要用纯 SQL 编写所有内容,但您也不能影响 SQL 查询的确切语句和顺序。所以可能有一些优化空间(比如:理论上这些约束可能有效),但 NH 不支持。我不会太担心这个。
    • @EvgeniyK:如您所见:这是唯一可行的解​​决方案,除了切换回本机 SQL。
    猜你喜欢
    • 2013-10-30
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 1970-01-01
    相关资源
    最近更新 更多