Is it possible to delete a collection of root entities with all their children using NHibernate - nhibernate

Here's my problem: I have a set of ids. These are the ids of a collection of root entities. Now I want to delete all these root entities, efficiently.
I can't do a WHERE Id IN (1, 2, 3) type of clause, as I'm deleting root entities with children.
I'm wondering if it's possible to avoid retrieving all root entities and deleting them one by one. The problem with that approach isn't so much the SELECT, it's have lots of separate DELETE statement.
Is it possible for NHibernate to batch this, including the delete of all the children? Extra complexity: children can have their own children.
So I'd want NHibernate to first delete the 'bottom-most' children with an IN-clause (maybe multiples ones), then the children with an IN-clause, and then finally the root entities with an IN-clause.
If this isn't possible, what's a good approach to delete multiple root entities efficiently with NHibernate?

UPDATE
This is not valid in your case where you had a list of ids (didn't read your question properly).
First, batch deletes of the "parents" can be made setting adonet.batch_size, http://nhibernate.info/doc/nh/en/index.html#performance-batch-updates.
END UPDATE
Secondly, to avoid reading the children (and generate deletes for these), you can set the cascading delete to happen on db level by setting ON CASCADE DELETE as a foreign key constraints.
Eg (note the on-delete attr)
<set name="TheCollection" inverse="true" cascade="save-update">
<key name="theKey" on-delete="cascade"/>
<one-to-many class="TheType"/>
</set>
Note that
this only works on inverse (bidirectional) relationships.
your db schema needs to be updated with this fk constraint
don't use cascade="all" or similar where deletes are included

Might help using the Session.Delete statement passing an array of IDs? I believe this will delete the whole map for those objects matching the IDs in the array.
Session.Delete("from MyTable t where t.ID in :IDs", IDs, NHibernate.NHibernateUtil.Guid);
where IDs is an array of Guids in this case, but could be any object.

Related

NHibernate prevent cascade delete

Suppose I have a class Foo. I also have a view on Foo called Foo_Foo that lists a many-to-many association between Foos. I mapped this association as a simple immutable set on each Foo, with cascade="none":
<set name="association" table="Foo_Foo" cascade="none" mutable="false">
<key column="ParentFoo" />
<many-to-many class="Foo, MyAssembly" column="BaseFoo" />
</set>
However, when I try to delete a Foo, NHibernate tries and rightly fails to delete the Foo.association.
How can I prevent NHibernate from trying to delete the association to a view?
The collection belongs to Foo. You can't share the collection, so there is no need to keep it in the database. Cascade is used to tell NH if the referenced Foos should be also deleted or not.
Why do you want the Foo_Foo records to keep in the database? If this should be a bidirectional many-to-many self reference, it doesn't work like this.
Edit, after understanding the question.
Cascade doesn't work in your case, because it affects only the referenced Foos.
To avoid inserts / updates and deletes of the collection table, you may try one of the following:
First obvious attempt is mutable="false", which you already tried. I don't really understand why it isn't working. You may ask in the Nhibernate user group.
Less obvious, but promising is inverse="true". Inverse tells NH that the collection is mapped somewhere else and doesn't need to be stored from here. So it just omits inserts, but I don't know about deletes.
If this doesn't work, you need to explore more complex solutions. You could map it as a one-to-many of an intermediate entity which references the Foos. The intermediate entity is a mapping to the view. It is immutable (which still may lead to delete statements). In this case, cascade="false" will work (because it is the referenced entity). It will also work configure insert, update and delete sql statements (which are empty), but this is most probably not even necessary.

NHibernate produces multiple queries on deletion of object in child collection

I have an object hierarchy as follows:
Object
Reports
Report Items
Based on other posts here, I set the Report.ReportItems mapping to Inverse, and CascadeAllDeleteOrphan. This makes it so when I remove a Report from an Object it goes to erase the ReportItems in that report.
The way it does it is very inefficient. It basically does a Delete ... Where ReportItemID = ?.
I've seen suggestions to increase the batch size to prevent this from being done in too many round trips, but that seems like a sloppy fix. Is there a way to make NHibernate generate a query like this instead:
Delete ... Where ReportID = ?
This way it would execute one query that would delete all the ReportItems instead of one statement per ReportItem.
Thanks in advance.
Edit
I have heard from a few people that NHibernate will simply not work this way with standard QueryOver etc.
I decided to start using HQL to fix the problem. I will later use some reflection to make sure there are no "magic strings" being used.
I had the idea to:
Use HQL to mass-delete the Report Items
Tell the Session to Refresh the object so it could detect that the Report Items are gone
Then tell the session to erase the Report, and let it clean up the remaining information
This does not work though. You can see the code below:
Session.CreateQuery("delete ReportItem r where r.Report= :report").SetEntity("report", SelectedReport).ExecuteUpdate()
Session.Refresh(SelectedReport)
Object.Reports.Remove(SelectedReport)
Session.Delete(SelectedReport)
Session.Update(Object)
I have also tried doing a Session.Evict after performing the HQL statements, but NHibernate keeps using the cache to try to delete the ReportItems. Any tips on how to do this?
If your foreign key has delete rule set to cascade then you can tell NH not to generate DELETE statements for child items when the parent is deleted. In this case NH will generate DELETE statement only for parent entity.
<bag name="ReportItems" cascade="all-delete-orphan" inverse="true">
<key column="report_id" on-delete="cascade" />
<one-to-many class="ReportItem" />
</bag>
After a lot of research I have found that this is simply not possible. You can use HQL, raw SQL statements, or rely on database rules to perform the deletes by enforcing a relationship. However, there is no way to tell NHibernate to delete a list of objects based on their owner's ID.

Single class maps to two table join

looking at an existing NHibernate implementation that maps a single class to two database tables that are joined. The current functionality is read-only. The table join is actually hidden away via a readonly view and it's the view that's referred to in the NHibernate mapping. Works fine for readonly behaviour. Except I need to add Insert, Update, Delete behaviour and only one of the tables needs to be inserted/updated/deleted. How best to do this? I can take readonly off the view of course, I could replicate the join in the NHibernate mapping, but how do I tell NHibernate that insert/update/delete affects one table only?
Thanks DC
You can set the update and insert attributes on the property mappings to false and they will be excluded from updates and inserts:
<property name="MyProperty" update="false" insert="false" />
Keep in mind that your view will need to have a single primary key defined to enable updates or inserts.
You can map to a view instead of a table, then define custom sql for your inserts, updates and deletes.
I would recommend calling a stored procedure.
See the docs: http://nhibernate.info/doc/nh/en/index.html#querysql-cud
I've used this technique quite a lot and it works fine.

Is it possible to suppress the Update after Insert in NHibernate?

I've been thrown into researching a database performance issue with a new application using NHibernate. I noticed that on some tables NH does an insert of a row followed by an update of the same row with exactly the same data. What I have gathered so far is that updates after inserts are done on tables that have many-to-one relationships defined.
In any case, I would have expected that NH figures that the data is the same and suppresses the extra update. Is there a way to suppress this extra update at all?
It sounds like you have a bidirectional relationship on a many-to-one association.
The solution to that problem is to add inverse="true" on the one-to-many side of the relationship. Here is an example.
The insert is performed by the child saving its data. The update is performed by the parent setting the parentId of the child record in the database. If the the relationship was uni-directional, the first insert would not have the parentId and the update would be required. With a bi-directional relationship, the update is superfluous as you describe. The inverse="true" tells the parent that the child is responsible for maintaining the relationship thus preventing the extra update.

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