删除对象图时的NHibernate异常:not-null属性引用null或transient值

时间:2011-07-08 06:12:04

标签: nhibernate cascade

我有一个计划(不需要字段):

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>

KeyAttribute:

<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'

所以我们可以看到:

not-null属性引用null或transient值:LogicalModel.KeyAttribute.Attribute 发生在NH检查字段属性(数据库中的非空约束,没关系),类 KeyAttribute 之后(参见profiler日志)。

这很有趣,因为NH必须删除属性和KeyAttributes两者,NH读取KeyAttribute类中属性字段的信息, FOUND 它在DB中, NOT FOUND 它在 NH会话(!!!)(因为之前加载了属性),只是抛出这个愚蠢的错误。

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

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

拜托,伙计们,有什么建议吗?

3 个答案:

答案 0 :(得分:1)

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

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

快速解决方案可能是保持延迟加载,但在删除时强制完全加载模型(使用hibernate initialize()),例如在Entity工厂中的Delete(Entity)静态方法中。

答案 1 :(得分:1)

您是否尝试在

中设置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,这是一个不可为空的

答案 2 :(得分:0)

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

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


或者,您也可以使用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中),则可以将参数列表分成几部分。
  • 直接在数据库中删除时会话失去同步。删除操作时,这不会导致任何问题。当您在同一会话中执行其他人员时,请在删除后清除会话。