How to set foreign key to null when referenced object deleted - nhibernate

I have a PhoneBook class that has a list of PhoneNumber. Then I have another class "Account" that references PhoneNumber. This reference is nullable, so an account doesn't have to have a phone number.
I would like to be able to save the PhoneBook, have it save/update/delete all the phone numbers, AND if any of the phone numbers are deleted, I want to null out any accounts using this number. Everything is working except for the last part; if I delete a phone number that's in use it deletes the account too, which I do not want to happen, I just want it to clear it's reference. I'm sure it's just because my cascades or mappings are wrong, I just don't know what to set them to.
The actual code to save is relatively simple. Incidentally, if someone can tell me why I have to Merge in order to make sure phone numbers are deleted that would be awesome.
var session = SessionFactory.GetCurrentSession();
book = (PhoneBookDto) session.Merge(book);
session.SaveOrUpdate(book);
Current relevant mappings:
PhoneBook.hbm.xml
...
<bag cascade="all-delete-orphan" inverse="true" name="PhoneNumbers">
<key>
<column name="phone_book_id" />
</key>
<one-to-many class="DataLibrary.dto.PhoneNumberDto, DataLibrary" />
</bag>
...
PhoneNumber.hbm.xml
...
<many-to-one cascade="none" class="DataLibrary.dto.PhoneBookDto, DataLibrary" name="PhoneBook">
<column name="phone_book_id" />
</many-to-one>
...
Account.hbm.xml
...
<many-to-one cascade="none" class="DataLibrary.dto.PhoneNumberDto, DataLibrary" name="PhoneNumber">
<column name="phone_number_id" />
</many-to-one>
...
Thanks in advance!
edit:
Firo pointed me in the right direction, I had to set up a collection on PhoneNumber back to Account even though I don't really need it, and also set it as inverse=false:
<bag cascade="none" inverse="false" name="Accounts">
<key>
<column name="phone_number_id" />
</key>
<one-to-many class="DataLibrary.dto.AccountDto, DataLibrary" />
</bag>
When I did that, it worked. Is there any way to do this so I don't have to have an association on PhoneNumber to Account?

"why I have to Merge in order to make sure phone numbers are deleted":
Either because you removed Phonenumbers out of changetracking (closed session with which the book was loaded) or because there is no Flush at the end while merge will immediatly flush the SaveOrUpdate will only add to the batch which is executed on next session.Flush().
"it deletes the account too"
maybe because of:
<bag cascade="all" class="Account" name="Accounts">
</bag>

Related

NHibernate constraint violated issue

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

NHibernate : Delete a record in many-to-many relationshio

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);

NHibernate exception while deleting object graph: not-null property references a null or transient value

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.

2 foreign keys in same entity mapping to same primary key problem

Hello hope someone can help me on this.
I have a situation where I have:
User entity which stores all my users including admins.
Comments entity which stores all comment against users.
my problem is I'm also storing within the comment entity the user id of the admin who made the comment. So now I have the Comment entity having 2 userId fields and when retrieving a User entity it wouldn't fetch any associating comments.
My mapping code is as below:
Within the User Entity..
<set name="MemberComments" cascade="all-delete-orphan" inverse="true">
<key column="UserCommentId"/>
<one-to-many class="IlluminatiCoreUserComment"/>
</set>
Within the UserComment Entity
<id name="UserCommentId">
<generator class="identity"/>
</id>
<many-to-one name="User" class="IlluminatiCoreMember" column="UserId" not-null="true"/>
<property name="Comment" not-null="true"/>
<property name="CreatedTimeStamp" not-null="true"/>
<property name="ModifiedTimeStamp" not-null="true"/>
<many-to-one name="CommentedByUser" class="IlluminatiCoreMember" column="CommentedByUserId" not-null="true" inverse="false"/>
How can I make MemberComments within the User entity point to just the User many-to-one field?
Thanks for your help in advance
The <key> element in User should have the FK to User, not the id of UserComment.
In other words, the same column as the many-to-one, UserId.

NHibernate Many to Many delete all my data in the table

I would love to thank #Stefan Steinegger and #David helped me out yesterday with many-to-many mapping.
I have 3 tables which are "News", "Tags" and "News_Tags" with Many-To-Many relationship and the "News_Tags" is the link table.
If I delete one of the news records, the following mappings will delete all my news records which have the same tags. One thing I need to notice, I only allowed unique tag stored in the "Tag" table.
This mapping make sense for me, it will delete the tag and related News records, but how can I implement a tagging system with NHibernate?
Can anyone give me some suggestion? Many thanks.
Daoming.
News Mapping:
<class name="New" table="News" lazy="false">
<id name="NewID">
<generator class="identity" />
</id>
<property name="Title" type="String"></property>
<property name="Description" type="String"></property>
<set name="TagsList" table="New_Tags" lazy="false" inverse="true" cascade="all">
<key column="NewID" />
<many-to-many class="Tag" column="TagID" />
</set>
</class>
Tag Mapping:
<class name="Tag" table="Tags" lazy="false">
<id name="TagID">
<generator class="identity" />
</id>
<property name="TagName" type="String"></property>
<property name="DateCreated" type="DateTime"></property>
<!--inverse="true" has been defined in the "News mapping"-->
<set name="NewsList" table="New_Tags" lazy="false" cascade="all">
<key column="TagID" />
<many-to-many class="New" column="NewID" />
</set>
</class>
When I run into trouble like that, the first thing I twiddle with is the cascade option.
As far as I know, the mapping is correct (I'm using mapping files that look exactly the same). The problem is the cascade attribute: the "all" option forces NHibernate to propagate each action on an entity to the instances of the collection. In your case, when you delete a news item all related tags are deleted too.
You probably should use "none" (in that case you'll eventually end up with some unused tags in the database) or "delete-orphans" (on the news item side - use "none" on the tag side).
Use the cascade option "save-update".
The option "all" will cascade deletes, which you do not want in this case. But you the option "none" will require that the Tag entity is already persisted which I guess might not always be the case.
So by setting the cascade to "save-update" new Tags till be inserted in the Tags table and in the link table News_Tags, but when you remove a tag from a News entity it will only be removed from the link table not the Tags table.