views:

61

answers:

3

I'm creating a windows forms application with NHibernate. It's an MDI application, so there is no limit to how many forms the user can have open at the same time (probably many).

For most forms I want to have an "OK" and a "Cancel" button. Both close the form, but "OK" also saves the modified data to the DB. The forms can be pretty complex, and the modifications are likely to touch a whole graph of objects, adding some, deleting some, and changing some more. It would be good if the changes could be automatically detected and persisted as needed, without the need to manually keep track of each of them.

What would be a good way to do this?

Extra information: I can make whatever DB schema I want. I'm using MSSQL 2008 and currently have decided for GUID primary keys (with guid.comb generator) and a TIMESTAMP column for optimistic concurrency.

I tried to simply set FlushMode of a NHibernate ISession to Never, doing all modifications as needed, and then calling Flush() if the user clicked OK. But that didn't work.

A: 

Maybe if you wrap the whole thing in a transaction and commit the transaction when the user clicks OK?

mmacaulay
And leave the transaction open the whole time the user works with the form? Think of the locking!
Vilx-
+1  A: 

A few possible solutions, assuming that you're using one ISession per form instance:

  1. Call ISession.Clear if the user cancels.
  2. Set the FlushMode to Commit and only initiate a transaction when the user clicks OK.
  3. Stick with your original approach of using Manual FlushMode. It should work and the behavior you're seeing is not a bug.

Since you're using GUIDs for primary keys, you should not run into the issue of NH unexpectedly flushing a session because it needs the database to generate an identifier. You still need to be aware of scenarios where NH may flush before a select to ensure consistent results.

I don't think you need to worry about opening and closing the database connection. My understanding is that NH is very good about managing that.

Jamie Ide
If it's not a bug, how should I work around it then? Because the the time when a user creates an object (say, by adding a row in a gridview) and the time when he modifies it is not at all related (he might come back to it whenever he pleases). When should I `Save()` the new object? Should I manually keep track of all new objects and `Save()` them only when OK is clicked?
Vilx-
It's not a bug because it's behaving as documented: http://knol.google.com/k/fabio-maulo/nhibernate-chapter-9/1nr4enxv3dpeq/12#9%282E%296%282E%29%28C2%29%28A0%29Flush. You should call `Save()` when the object is in a state in which it can be inserted to the database. NHibernate may defer the insert until `Flush()` or, in the case of generated IDs, it may insert immediately. If the user can add/change/delete rows in the grid then I would wait until they clicked OK to figure out which ones need to be inserted. It depends on the application.
Jamie Ide
@Jamie Ide - Well, exactly. I have to keep track of dirtiness myself. Or at least the insertions I guess. By the way - what do you mean it's documented? I don't see this particular behavior mentioned there - directly or indirectly.
Vilx-
The documentation I provided a link to describes how Flush works in detail. The behavior you asked about in your other question is an outcome of the order described in "The SQL statements are issued in the following order". Of course it is up to your code to decide when to make an object persistent by calling Save. Why do you want to track dirty anyway? Either of the three solutions I offered will work regardless of the session having pending changes.
Jamie Ide
I want to track dirty because I want to have a simple way of implementing the OK/Cancel buttons. If NHibernate would generate the SQL's at the time of Flush()'ing, I wouldn't have to write any code at all. Maybe I better give an example. I'm currently trying to make a form for editing a simple classifier. The DB table and has just three columns - ID, Name and Version. The same goes for the object, naturally. The form I use consists of a single GridView with one column - Name. The user can add/delete/change rows in it.
Vilx-
The elegant way of implementing this would be to allow NHibernate handle all of it. I just place a grid, bind it to my list of items in the contructor, and then handle the insert/delete/change events by replicating the changes to the session. Then I can call Flush() when the user hits OK, and everything happens automagically.
Vilx-
That should work. But here's how I would handle that scenario: Bind the grid to the collection of items and hold the items collection in memory. Maintain the items collection as the user interacts with the grid. When the user clicks OK, start a transaction, loop through the collection and check if the item is persistent using ISession.Contains, Save the object if it is transient, and commit the transaction. Another option is to close the original ISession and open a new one on OK, then call SaveOrUpdate on every item in the collection.
Jamie Ide
The biggest problem I see with the way you want to work with NHibernate is that you could possibly fill the session with "junk" objects. For instance the user could add and delete rows causing NHibernate to insert and delete junk data in the transaction.
Jamie Ide
Hmm... I've been thinking some more and I think that what I want... what would be ideal, would be some kind of "long runnining offline transaction". A view at the DB which reflects my changes, but doesn't really commit them. So, for example, if I "insert" an item, it isn't really inserted (until I call flush), but the appropriate "select" queries would return it along with others from the DB. "deleted" objects would be hidden and not returned in "select" queries. And so on and so forth. NHibernate definately doesn't provide this, and it would be pretty difficult to do.
Vilx-
+1  A: 

This should help: NHibernate Best Practices with ASP.NET, 1.2nd Ed. (I know it's ASP.NET, but you should find the information useful and easily transferable to WinForms.)

In short, one should opt for a sesson per window architecture. Making impossible to open two different windows for the same exact task makes sense here. Then, you perhaps want to create an instance of the NHibernate ISession API on Form_Load(). At the end, if the user clicks OK, then just BeginTransaction(), Flush() the session, and Commit() the transaction, otherwise rolls it back.

Will Marcouiller
Bad idea. NHibernate transactions are just thin wrappers around ADO.NET transactions. Can you imagine what would happen if a transaction was being held open all the time while the user worked with a form? How many DB tables would it lock?
Vilx-
You're right, then what I really meant is keeping the session open, with no active transaction. But everytime you load from or write to the database, do it through a transaction. I have edited my answer to reflect what I meant.
Will Marcouiller