views:

51

answers:

2

When I do this:

Cat x = Session.Load<Cat>(123);
x.Name = "fritz";
Session.Flush();

NHibernate detects the change and UPDATEs the DB. But, when I do this:

Cat x = new Cat();
Session.Save(x);
x.Name = "fritz";
Session.Flush();

I get NULL for name, because that's what was there when I called Session.Save(). Why doesn't NHibernate detect the changes - or better yet, take the values for the INSERT statement at the time of Flush()?

Added: To clarify: The Session.FlushMode is set to None, so there are no automatic flushes until I say so. I'm also using GUID primary keys (with guid.comb generator).

The reason I'm doing this is because I'm using the Session as a "dirtiness" tracker. I'm writing a Windows Forms application and every one of my forms has a separate session which lasts as long as the form does. The session is kept disconnected as much as possible so that I don't run out of ADO.NET connections (it's an MDI application and there's no limit how many forms can be opened). Every form also has an "OK" button and a "Cancel" button. The "OK" button calls Session.Flush() and then closes the form; the "Cancel" button just closes the form and silently discards all changes the user has made.

Well, at least that's what I would like. The above bug is giving me problems.

A: 

Unless you have a very good reason not to, you have to use a transaction instead of an explict flush call.

using (var session = createSession())
{
   using (var transaction = session.BeginTransaction())
   {
      Cat x = new Cat();
      session.Save(x);
      x.Name = "fritz";
      try
      {
          transaction.Commit();
      }
      catch
      {
          // prevents your database from getting corrupt when you have a bug
          transaction.RollBack();
          throw;
      }
   }
}

In a real application, it is a good practice to hide the transaction creation, commit and roll-back in a separate part in your application, so that you don't have to call it in each data-access block. In most applications with a relation database, one transaction is wrapped around the whole code that processes one screen for a user. This causes all actions made by the user to succeed, or fail when there is a bug, as an atomic block. All data changed by the user in a screen is stored, or nothing is stored, never parts of it.

Paco
True, but I'm trying to use NHibernate in a bit different way. I'm making a Windows application and I'm using the session for change tracking. That is, when a form is opened, a session is started. It's kept disconnected except when data needs to be retrieved. The form has a "OK" button and a "Cancel" button. When "OK" is clicked, I call `Flush()`. If "Cancel" is clicked, the session is silently disposed and all changes lost. It would work, except for this issue...
Vilx-
Naturally, I also set `FlushMode` of the session to `Never` so that there are no accidental commits before the user clicks "OK".
Vilx-
Presentation model != domain model
Paco
When you set flushmode to never, no changes are tracked.
Paco
That's not correct. FlushMode Never has been deprecated in favor of Manual, but they have the same effect: "The Session is only ever flushed when Session.flush() is explicitly called by the application".
Jamie Ide
@Paco - Well, how would you implement it then? Add another changetracker yourself?
Vilx-
The session is a change tracker. I don't need much code for the implementation. In a webapp, I use one session per request, and I create and commit the transaction in the global.asax begin-request and end-request handlers.
Paco
Well, that's a webapp. But even there - what do you do with the following requirements: The form should have a grid where the user can add/delete rows. They should be saved only when the user clicks "OK". What then? Where do you store the data inbetween? How do you detect changes there? (For a fair measure, let's suppose there are some things that require postbacks to the server as well)
Vilx-
I would use one session and one transaction to commit all changes for the screen at once. There is no between. I don't have to track changes before the user hits save. By the way: grids are so 1990's. We can create more useful friendly user-interfaces these days, task based instead of data-entry based.
Paco
A: 

NHibernate does track the changes, but your code as written will cause it to issue an insert with the values at the time you called Save and an update for the changes made after calling Save. Save makes the object persistent (insert), then NHibernate begins tracking changes made to the persistent object (update). Both the insert and the update are committed when the session is flushed.

We're facing similar issues with our Windows Forms application and I think we're going to take a different approach. The FlushMode is left at Auto (default) and the object is evicted (ISession.Evict) from the session if the user cancels the operation. Evicting the object makes it transient which is exactly the desired behavior.

Jamie Ide
Wait, does that mean that you INSERT the object in the DB, and then later DELETE it if the user cancels?
Vilx-
No way. It means that we the object is evicted from the session if the user cancels the operation so that no changes are persisted if the session is flushed. Typically that marks the end of the session as well. If needed the object is then retrieved again for display in its previous state.
Jamie Ide
Oh, OK. But what happens then if NHibernate decides to Flush your session, and then the user cancels? Actually - what's the lifetime of your sessions? And - you do have an MDI application too, right?
Vilx-