(Fluent) NHibernate: force foreign key to null on delete - nhibernate

I have a standard parent - child (1:many) relationalship, configured using Fluent NHibernate:
On the parent side:
HasMany(x => x.Items).Inverse().Cascade.All();
and on the child side:
Map(x => x.ItemCategory).Nullable().Index("idx_item_category").Not.LazyLoad()
(Edit in response to epitka's comment:)
The record is deleted by calling
session.Delete(item_category)
This is the only operation done in the transaction.
(End Edit)
Currently when I delete an ItemCategory record it cascade the delete to all the items, which appears to be working as expected according to the documentation.
What I want is for Item.ItemCategory to be set to null automatically when the ItemCategory record is deleted.
I can only seem to turn off the cascade completely, which leads to a broken database (item's referencing a missing category). So, currently I have to do this manually which is a little more error prone than I'd like.
Is it possible to configure this behaviour?
session.Delete(item_category)

Whil it's not possible to do that out of the box, you can probably implement an IPreDeleteEventListener that fires an HQL update to set the Items' ItemCategory to null.

Related

Fluent Nhibernate --- How to make it NOT update the other table

I have a [User] table/class and a [Company] table/class and there is a link-table [UserCompany] between them.
When editing a User, beside basic information people also could change that user's access Companies, so I do the map like this in UserMap.cs:
HasManyToMany(u => u
.Companies)
.Cascade.SaveUpdate()
.Table("UserCompany")
.ParentKeyColumn("UserId")
.ChildKeyColumn("CompanyCode")
.Not.LazyLoad();
Also in CompanyMap.cs I set inverse like this:
HasManyToMany(c => c.Users)
.Inverse()
.Table("UserCompany")
.ParentKeyColumn("CompanyCode")
.ChildKeyColumn("UserId");
The problem now is: I could update [User] information/table, plus the linking data in [UserCompany] table. However, the Fluent Nhibernate also update the [Company] table which I don't need at all.
Is there any way I could let FN not update Company table?
To stop cascading updates just remove the
.Cascade.SaveUpdate()
from your Many-to-many mapping.
It could be a bit confusing. In comparison with the cascading used on <list>s and <map>s. In that case, the update is done directly on the child table (parent has more children... child contains the ParentId - cascade is reasonable to do operation on the child record)
But here we are working with a pair table. The relation is stored there. And this table will be always managed by NHibernate (implicit cascading).
The setting .Cascade.SaveUpdate() goes to other end of the many-to-many relation. To company table in our case. It could be handy.. but you can omit that and get everything running as expected.

Delete in HasManyToMany

I have three table -
1. Anomaly
2. Markup
3. Anomaly_Markup
Mapping -
public AnomalyMap()
{
Table("anomaly");
Id(x => x.Id).Column("ID").CustomType("decimal");
HasManyToMany<DMMarkupData>(x => x.DMMarkupData)
.Table("anomaly_markup")
.ParentKeyColumn("ANOMALY_ID")
.ChildKeyColumn("MARK_UP_ID")
.Cascade.All()
.LazyLoad();
}
public MarkupDataMap()
{
Table("markup");
Id(x => x.Id).Column("ID");
}
Condition :
Save data by Anomaly - Anomaly contains MarkupData. It saves data. It is working functionality with me.
Delete markup - which should delete relationship from map table and markup data. I am facing this issue.
Anyone help me to find out solution, how to delete markup data ?
I see no relationship of MarkUpData with Anomaly. There must be same relationship and you should specify the control of cascade operation by using Inverse attribute in your mapping.
You can refer : How to set "cascade delete" option to "Set Null" in Fluent NHibernate?
To delete DMMarkupData just remove the object from collection and call for Save Anomaly.
From what you've posted it looks like you haven't defined a relationship on DMMarkupData -> Anomaly, so NHibernate won't know to delete it the MarkupData entries from the anomaly_markup table (despite the reverse relationship being there). You can either solve it with a database level cascade which removes entries in anomaly_markup when deleting MarkupData, or you can map the relationship in code & NHibernate and then NHibernate will do the cascade for you.
NHibernate does not manage the object graph, it only persists it. Removing the item from the list when it gets deleted is responsibility of the business logic!
(All the tricks with triggers and stuff that workaround it lead to inconsistencies and side effects within the transaction which does the change. From point of view of persistence ignorance it is not recommended. I would only do it when facing performance issues that can't be solved in another way.)
You can simplify it by using components. Provided that
you don't reference the same markup from somewhere else
you don't need to query for markup unrelated to Anomality
markups do never live outside of an Anomality
given all that, it is much easier to work with components. (I don't know how it is called in fluent, but in xml mapping it is called a "composite-element").
When using components, you don't need to delete the markup from the database. You just remove it from the list where it lives in.

Can't cascade delete or update in Nhibernate if several Entities has references to the same source

I have such mapping of ChargeOperations(left table) and Distributions(right table):
In code mapping of ChargeOperations looks like:
HasMany(x => x.Distributions).Table("ShadowDistributions").KeyColumn("SourceId").Cascade.All().Inverse();
ShadowDistributions - is a right table. x.Distributions is a just a list of Distributions(right table). X - is ChargeOperation(left table)
Mapping of Distributions (right table)
References(x => x.Source).Nullable().Column("SourceId").Not.LazyLoad();
References(x => x.Dest).Nullable().Column("Dest").LazyLoad().Fetch.Join().Cascade.All();
So, I want to delete just one row from Distributions (right table).
And the applications throws different mapping exceptions like "Transaction could not commit because of a failed resource : deleted object would be re-saved by cascade (remove deleted object from associations)[ChargeOperation#58]"" or "Unexpected row count: 0; expected: 1" and so on.
I use cascade for creating entities and it works great, but for delete I had to
clean all references in the right table, and after that save all types of entities separately. If not, I'll get errors.
But I'd like to use cascade saving. How can I realize it?
Possible variants after save:
deleted just one record from right table. all records in left
table are exist
deleted just two records from right table. all
records in left table are also deleted
Change your Cascade to Cascade.SaveUpdate() if you don't wish to delete any of the referenced entities.
In my opinion you shouldn't be trying to delete a parent (ChargeOperations) whenever you delete one of the children (Distributions)
Example
public SomeMethod()
{
using(ISession session = ... //Get my session from somewhere)
{
Distribution childToDelete = ... //Get the distribution to delete
ChargeOperation parent = ... //Get the parent of the distribution we are deleting
parent.Distributions.Remove(childToDelete);
//Since the parent is in session just flush the session to apply changes
session.Flush();
}
}

NHibernate One-to-Many - why is it updating child with null foreign key?

I am troubleshooting code that is attempting to update a disconnected entity that has uninitialized references to child entities. The intent is to update only the properties on Parent without loading children.
HasMany(x => x.ChildEntities)
.KeyColumn("ChildEntityId")
.Table("ChildEntity")
.Not.LazyLoad()
.Inverse()
.Cascade.All().AsBag();
When Session.Update(parent) is called, two update statements are executed. The first updates the parent object as expected.
update Parent set ... where ParentId = 12345
The second update confuses me...
update ChildEntity set ParentId = null where ParentId = 12345
Why is NHibernate issuing that second SQL statement? I realize that ChildEntities is uninitialized and that NHibernate is probably trying to enforce the state of Parent but I can't seem to tweak the mapping to not make this second update. I've tried Merge, lazy loading, various cascade options, etc. without success. The only connected entity in the session when it tries to commit is Parent.
Note that I typically approach this by retrieving the entity with lazy loading enabled and then mapping from the disconnected object (DTO or entity) to the connected entity before letting NHibernate persist to the database. I want to understand why the above isn't working before I suggest an alternative approach.
This was annoying.
A quick search through the NHibernate source for "could not delete collection" showed up in a block that could only execute if !isInverse (AbstractCollectionPersister.cs). That drew my attention because the mapping code was explicitly setting Inverse on that collection.
If Inverse is false and the collection is empty, NH executes an update on the child table setting the foreign key to null where the foreign key equals the parent id.
Fluent is configured to auto-map all entities in a given namespace. The assumption was that anything with a manual mapping would be ignored by auto mapping. A quick check of the hbm.xml files produced by Fluent confirmed that Inverse was not being set. I added Parent to the list of entities that were explicitly excluded from Auto Mapping and everything started working.
.IgnoreBase<Parent>()

How to delete child object in NHibernate?

I have a parent object which has a one to many relationship with an IList of child objects. What is the best way to delete the child objects? I am not deleting the parent. My parent object contains an IList of child objects. Here is the mapping for the one to many relationship:
<bag name="Tiers" cascade="all">
<key column="mismatch_id_no" />
<one-to-many class="TGR_BL.PromoTier,TGR_BL"/>
</bag>
If I try to remove all objects from the collection using clear(), then call SaveOrUpdate(), I get this exception:
System.Data.SqlClient.SqlException: Cannot insert the value NULL into column
If I try to delete the child objects individually then remove them from the parent, I get an exception:
deleted object would be re-saved by cascade
This is my first time dealing with deleting child objects in NHibernate. What am I doing wrong?
edit: Just to clarify - I'm NOT trying to delete the parent object, just the child objects. I have the relationship set up as a one to many on the parent. Do I also need to create a many-to-one relationship on the child object mapping?
You are getting the first error because, when you remove the items from the collection, NHibernate's default mode of operation is to simply break the association. In the database, NHibernate tries to set the foreign key column on the child row to null. Since you do not allow nulls in that column, SQL Server raises the error. Clearing the collection will not necessarily delete the child object, but one way to do so is to set cascade=all-delete-orphan. This informs NHibernate that it should delete the newly orphaned rows instead of setting the foreign key column.
You are getting the second error because when you call SaveOrUpdate NHibernate first deletes all of the child objects. Then, because neither relationship is marked as inverse, NHibernate also tries to set the foreign key column in your child table to null. Since the rows have already been deleted, you receive the second error. You need to set inverse=true on one side of your relationship to fix this. This is usually done on the one (primary key or parent) side. If you do not do this, NHibernate will make the appropriate updates for each side of the relationship. Unfortunately, running two updates is not the appropriate thing to do.
You should always mark one side of your relationships as the inverse side. Depending on how you code, you may or may not need to use cascading. If you want to take advantage of one shot deletes as you are trying to do using Clear(), you need to define your cascade.
Acording to Chuck's answer, I've resolved my problem by adding Inverse = true in parent side mapping:
Message has many MessageSentTo:
[HasMany(typeof(MessageSentTo), Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, Inverse = true)]
public IList<MessageSentTo> MessageSendTos
{
get { return m_MessageSendTo; }
set { m_MessageSendTo = value; }
}
I am using Castle ActiveRecord. Thank you Chuck.
Try using merge() instead of saveOrUpdate(). Also, make sure your cascade is set to all-delete-orphan and that your parent-child relationship is invertible (inverse=true on the parent and then a field in the child that is the parent-id with not-null=true).
In our example we have categories with many products where a product is not nullable.
You can work around the problem by deleting the product and removing it from the parent's collection before the flush but we're still looking for a better solution to this.
product = pRepo.GetByID(newProduct.ProductID);
product.Category.Products.Remove(product);
pRepo.Delete(product);
Hope it helps anyway
Change cascade attribute value from "all" to "all-delete-orphan".
set Not-Null = true in your mapping on the column causing the issue. I'm not sure of the exact syntax though (sorry).