11

I'm using NHibernate with Fluent NHibernate.

I have code where I start a transaction, then I enter a loop which creates several objects. For each object I check certain conditions. If these conditions are met, then I execute a session.SaveOrUpdate() on the object. At the end of the loop, I issue a commit transaction.

I have a breakpoint set on the session.SaveOrUpdate command, proving that it is never reached (because the conditions have not been met by any of the objects in the loop). Nevertheless, when the transaction is committed, the objects are saved!

I am using an AuditInterceptor and have set a breakpoint in the OnSave method. It is being called, but the stack trace only traces back to the statement that commits the transaction.

There are no objects of any kind that have had SaveOrUpdate executed on them at this point, so cascading doesn't explain it.

Why is NHibernate saving these objects?

MylesRip
  • 1,212
  • 17
  • 29

2 Answers2

15

From NHibernate ISession.Update thread:

It's the normal and default behavior:

Hibernate maintains a cache of Objects that have been inserted, updated or deleted. It also maintains a cache of Objects that have been queried from the database. These Objects are referred to as persistent Objects as long as the EntityManager that was used to fetch them is still active. What this means is that any changes to these Objects within the bounds of a transaction are automatically persisted when the transaction is committed. These updates are implicit within the boundary of the transaction and you don’t have to explicitly call any method to persist the values.

From Hibernate Pitfalls part 2:

Q) Do I still have to do Save and Update inside transactions?

Save() is only needed for objects that are not persistent (such as new objects). You can use Update to bring an object that has been evicted back into a session.

From NHibernate's automatic (dirty checking) update behaviour:

I've just discovered that if I get an object from an NHibernate session and change a property on object, NHibernate will automatically update the object on commit without me calling Session.Update(myObj)!

Answer: You can set Session.FlushMode to FlushMode.Never. This will make your operations explicit ie: on tx.Commit() or session.Flush(). Of course this will still update the database upon commit/flush. If you do not want this behavior, then call session.Evict(yourObj) and it will then become transient and NHibernate will not issue any db commands for it.

Community
  • 1
  • 1
rebelliard
  • 9,592
  • 6
  • 47
  • 80
  • According to the above, "Save() is only needed for objects that are not persistent (such as new objects)." The objects in question that are being saved _are_ new objects, however the code does set up a relationship with an existing object so maybe that's why it's being saved. – MylesRip Mar 20 '11 at 03:54
  • It looks like I will need to specifically evict the ones I don't want and possibly also set the FlushMode to Never. I'll give that a shot... – MylesRip Mar 20 '11 at 04:08
  • Looks like it won't be that simple. The objects I don't want to save are throwing an exception in the constructor. So my pointer to the new object is null. Since I can't use session.Evict(null) to evict the object, NH still saves it. I capture the exception and log it, but I don't want to kill the process. I also don't want to rollback the transaction because I'm handling 100 objects per transaction for performance reasons and I need to keep and persist any valid objects that are in the transaction. – MylesRip Mar 21 '11 at 17:06
5

It's to do with the sessions flush mode being FlushMode.Commit (default). When the transaction is committed any changes made to objects within the session are saved and the changes persisted.

There's a FlushMode property on the session that you can set. If you want a readonly transaction specify FlushMode.Manual.

Hope this helps!

Sam Fraser
  • 51
  • 3
  • Thanks, Sammo, that does help. What I need isn't a read-only transaction. I need to be able to create several objects within the transaction and selectively save only some of them. – MylesRip Mar 20 '11 at 04:05
  • Potentially you could alter the cascade on the parent's collection to not save/update... I'd expect then you could call "save" on the individual child objects you want to persist. But I'd be careful as changing the cascading behaviour could have implications elsewhere! – Sam Fraser Mar 20 '11 at 22:12
  • I don't want to mess with the cascade behavior. It would cause other problems. Also, since there are other objects in the transaction I need to save, I will still need to perform a flush, whether it's manual or automatic. When I do that, I'll still have the same problem of the unwanted objects being saved. Controlling the timing of the flush doesn't change that. It seems that evicting the unwanted objects using session.Evict(object), as suggested by binaryhowl below, is the only way to keep them from being saved. (There are still issues in my situation as I note in my comments on his post.) – MylesRip Mar 22 '11 at 02:23