Исключение NHibernate при удалении графа объекта: свойство not-null ссылается на нулевое или временное значение - PullRequest
1 голос
/ 08 июля 2011

У меня есть схема (поля не обязательны):

занятой кот http://picsearch.ru/share/image-BCE8_4E168F3B.jpg

У меня есть сопоставления:

Entity

<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 - атрибут , так что это просто много-к-одному, и мне вообще не нужна обратная связь.

Теперь проблема в том, что я пытаюсь удалить весь график - удалить объект Entity (обратите внимание: Entity на самом деле вообще не загружаются, это просто набор прокси, поэтому 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 Profiler:

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 ссылается на нулевое или временное значение: LogicalModel.KeyAttribute.Attribute произошло сразу после проверки поля NH Атрибут (нет-нулевое ограничение в БД, все в порядке) в классе KeyAttribute (см. журнал профилировщика).

Это довольно забавно, потому что NH должен удалить оба атрибута и KeyAttributes, NH читает информацию о поле Attribute в классе KeyAttribute, FOUND это в БД, NOT FOUND это в NH session (!!!) (потому что Атрибуты были загружены раньше), и просто выбросьте эту глупую ошибку.

Что я уже пытался сделать: 1. make not-null = "false". В этом случае NH делает дополнительное обновление - попробуйте установить Attribute = NULL - вызвать нарушение ограничения в БД. 2. установить lazy = "false", lazy = "no-proxy" для связи многие-к-одному для KeyAttribute-Attribute - ничего;

Теперь мне не нравится идея перехватчиков, потому что во многих ситуациях у меня такая же ситуация, мне нужно общее решение

Пожалуйста, ребята, есть предложения?

Ответы [ 3 ]

1 голос
/ 08 июля 2011

Вы пробовали установить on-delete = "каскад" в

<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

1 голос
/ 08 июля 2011

На мой взгляд, это может быть вызвано вашей ленивой нагрузкой на все сущности модели.При удалении сущности он загружает и удаляет ссылочный список атрибутов, загружает ссылочный список ключей, загружает ссылочный список KeyAttribute (чтобы иметь ключ удаления), а затем он попадает в свойство not-null, ссылающееся на нулевое или временное значение, так как ссылочный атрибут был удален ранеев сеансе.

Вы можете проверить это, удалив всю ленивую загрузку из файлов сопоставления.

Быстрое решение может состоять в том, чтобы сохранить ленивую загрузку, но принудительно загрузить полную модель (с инициализацией hibernate)()) при удалении, например, статическим методом Delete (Entity) в фабрике Entity.

0 голосов
/ 08 июля 2011

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).
  • Сессия не синхронизируется при удалении непосредственно в базе данных. Это не вызывает никаких проблем, когда удаление - это все, что вы делаете. Когда вы работаете с другими сотрудниками в том же сеансе, очистите сеанс после удаления.
...