I've got a legacy schema with a main table and secondary table, where the secondary table connects to the main table by having the same primary key (a Secondary doesn't not necessarily exist for a given Main). I've been searching up-and-down for XML mappings that will make this work but haven't found anything that works for me.
<class name="Secondary" table="Secondary" lazy="true" dynamic-insert="true" dynamic-update="true">
<id name="mainId" type="Int32">
<column name="MAIN_ID" not-null="true" />
<generator class="foreign">
<param name="property">Main</param>
</generator>
</id>
<one-to-one class="Main" name="Main" constrained="true" />
</class>
<class name="Main" table="Main" lazy="true" dynamic-insert="true" dynamic-update="true">
<one-to-one name="Secondary" cascade="all-delete-orphan" class="Secondary" />
Also tried on the Main side, still no go. It doesn't necessarily break, but it certainly doesn't do what I expect. For example:
session.Query<Main>().Count(m => m.Secondary != null) generates
select
cast(count(*) as INT) as col_0_0_
from
MAIN main0_
where
main0_.MAIN_ID is not null
Note that it's using the MAIN_ID from the MAIN table and is ignoring Secondary altogether.
This issue is fixed in NHibernate 5.3
It's a known issue (PR with suggested fix is here).
For now as a workaround in LINQ you can call Count on some non-ID and not-nullable property:
session.Query<Main>().Count(m => m.Secondary.NotNullableProperty != null)
If such property doesn't exist (or you just want to easily find all such hacky usages in future) you can additionally map your Id column as read-only property and use it instead:
<property name="ForceJoinId" not-null="true" column="MAIN_ID" insert="false" update="false" />
session.Query<Main>().Count(m => m.Secondary.ForceJoinId != null)
I'm working with NHibernate and QueryOver. I have an aggregate root for my aggregate named Parent and two kinds of child entity. I have Child entities that are parts of my aggregate, and QUChild entities that are not parts of my aggregate, and are just used for JOIN clause in QueryOver.
How to distinguish between two child entities in mapping file?
<class name="Parent" table="Parent" schema="dbo">
<bag name="Childs" inverse="true" cascade="all-delete-orphan" />
<key>
</key>
<one-to-many class="Child" />
</bag>
<bag name="QUChilds" /> <!-- which attribute must be set to do nothing? -->
<key>
</key>
<one-to-many class="QUChild" />
</bag>
</class>
Well, just do nothing with it. NHibernate will not eager load, nor lazy-load it if you do not access it from your loaded parent entities.
And default cascade is none, so just leave it as you have mapped it. (But I would add inverse="true" just in case code changes lead to add some children in that collection too, later on.)
There are two entities in my code which are participated in a many-to-one relationship. The problem is when i try to delete the parent it says :
ORA-02292: integrity constraint violated - child record found
As you can see below there is a User Security Parameter entity in my project which can have related children called Exceptional User Security parameters.
I expect the ORM to delete the found child records when it wants to eliminate their parent
<bag name="ExeptionalUserSecurityParameters" inverse="true" lazy="false" access="property" cascade="none" batch-size="256">
<key>
<column name="Key" />
</key>
<one-to-many class="ExeptionalUserSecurityParameter"/>
</bag>
<many-to-one name="UserSecurityParameter" cascade="all-delete-orphan" fetch="join"
class="UserSecurityParameters" >
<column name="Key" />
</many-to-one>
How can I avoid this issue?
Almost always (well, always) I do use cascading like this:
//<bag name="ExeptionalUserSecurityParameters" cascade="none" ...
<bag name="ExeptionalUserSecurityParameters" cascade="all-delete-orphan" ...
//<many-to-one name="UserSecurityParameter" cascade="all-delete-orphan"
<many-to-one name="UserSecurityParameter" cascade="none"
that should solve the issue. If the collection owner is deleted.. who ever reference it - is deleted as well. But not vice versa
I have a object Customer, this object has an ISet list of Contact. When I delete a Customer I'd like to delete the Contact.
I use the mapping below, I tried all option in cascade but still have this problem :
The DELETE statement conflicted with the REFERENCE constraint "FK4FF8F4B29499D0A4". The conflict occurred in database "MyDB", table "dbo.Contact", column 'Customer'.
The mapping Customer
<set name="Contacts" table="CustomerContact" cascade="save-update">
<key column="Customer" />
<many-to-many class="Contact" column="Contact" />
</set>
The mapping Contact
<many-to-one name="Customer" column="Customer" not-null="true" />
It is strange that you have bidirectional association between customer and contact mapped like that. If Customer can be associated with multiple Contacts, and vice versa, you should have many-to-many on both sides. But you have many-to-one at Contact side. And you mention that you want to cascade deletes to Contact.
Perhaps you should consider mapping Contacts collections as one-to-many? Try this for Customer mapping, note inverse attribute.
<set name="Contacts"
table="CustomerContact"
inverse="true"
cascade="all-delete-orphan" >
<key column="Customer" />
<one-to-many class="Contact" />
</set>
With this Contact mapping:
<many-to-one name="Customer" column="Customer" />
You will also have to 'chase the pointers': null out Customer.Contact when corresponding Contact is removed from Customer.Contacts collection.
having the two propeties
inverse="true"
and
cascade="all-delete-orphan"
is the key..
apart from this you can as well do this while deleting the customer object:
customer.Contacts.Clear();
Session.Delete(customer);
I've got a scheme (fields aren't necessary):
a busy cat http://picsearch.ru/share/image-BCE8_4E168F3B.jpg
I've got mappings:
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>
Attribute
<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>
Key
<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>
Now please take a look...
As you see, We've got one-way master association KeyAttribute - Attribute, so it's just many-to-one and I don't need back association at all.
Now the problem is when I'm trying to delete whole graph - delete Entity object (notice: Entity actually aren't loaded at all, it's just set of proxies, that's why NHibernate make additional SELECT queries to check references before delete)
like this
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'
So what we can see:
not-null property references a null or transient value: LogicalModel.KeyAttribute.Attribute
happened just after NH check field Attribute (not-null constraint in db, it's ok) in class KeyAttribute (see profiler log).
It's pretty fun, cause NH have to delete Attributes and KeyAttributes both, NH read information about Attribute field in KeyAttribute class, FOUND it in DB, NOT FOUND it in NH session (!!!) (cause Attributes was loaded before), and just throw this stupid error.
What I've already tried to do:
1. make not-null="false". In this case NH makes additional update - try to set Attribute=NULL - cause constraint violation in DB.
2. set lazy="false", lazy="no-proxy" on many-to-one association for KeyAttribute-Attribute - nothing;
Now I don't like the idea of interceptors because there are to many scenarios where I've got the same situation, I need common solution
Please, guys, any suggestions?
In my opinion it may be caused by your lazy load on all entities of model.
When deleting entity, it loads and delete referenced Attribute list, loads referenced Key list, loads referenced KeyAttribute list (to have key of deletion) and then it falls in not-null property references a null or transient value because referenced Attribute has been deleted before in session.
You can check that by removing all lazy load in your mapping files.
A quick solution may be to keep lazy load but to force a full load of model (with hibernate initialize()) when deleting, for example in a Delete(Entity) static method in Entity factory.
Have you tried setting on-delete="cascade" in
<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" />
Because in profile you will see nh trying to update something to null which is non nullable
NH sometimes requires to set references to null. Usually this is to avoid problems in models where circular references exist. But it is not always clever enough to find a way to avoid it, even if the is one.
So it may require to allow nulls in some foreign key fields, of course not only in the mapping file, also in the database. It actually should solve the problem.
Alternatively, you could also delete the data table by table using HQL. This works fine in all cases where you don't have inheritance and if you know all entities and the order to delete them:
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();
Note:
You may break the parameter lists into piece if they exceed the size of around 2000 (in SQL Server).
The session gets out of synch when deleting directly in the database. This doesn't cause any problems when deleting is all you do. When you are doing other staff in the same session, clear the session after deleting.