views:

815

answers:

5

The default behavior of NHibernate is the write all changes to objects to the database when Session.Flush() is called. It does this whether you want it to or not.

How do we prevent writing bad data to the database when we need to do things like validate business rules or input?

For instance ..

  • Customer Name is not null.
  • User opens a web browser (without javascript) and deletes the customer name.
  • Hits update.
  • Customer.Name property is updated and ..
  • Customer.IsValid() is called.
  • Even if IsValid() is false and we show error messages NHibernate still updates the database.
+1  A: 

Use the ISession.Evict(objectToEvict) method to evict invalid objects.

See: http://www.tobinharris.com/2007/2/3/nhibernate-faq and http://www.surcombe.com/nhibernate-1.2/api/html/M_NHibernate_ISession_Evict.htm

Nathan
agreed, that works but then I'm calling evict all over my client code. not ideal.
Kyle West
A: 

You can also use a Session with FlushAction.Never. Ie:

SessionScope session = new SessionScope(FlushAction.Never);

This will switch the default behaviour from automatically saving everything to you explicitly needing to call .Save() on your entities. So you can do whatever validation you need to do and only then save what you want to...

KevinT
Save does not mean "save the changes." Changes are automatically saved behind the scenes (that's what Flush does). Save changes the state of an object from transient to persistent, and cascades the changes as instructed to by the object's mapping; it does not necessarily execute SQL immediately.
Justice
I'm using this in a web application, so I don't have direct access to the session (it is handled by the unit of work).
Kyle West
A: 

The default behavior of NHibernate is the write all changes to objects to the database when Session.Flush() is called. It does this whether you want it to or not.

If you don't want NHibernate to flush the session, then why are you telling it to flush the session? Flush() is shorthand for WriteAllChangesToObjectsToTheDatabase().

How do we prevent writing bad data to the database when we need to do things like validate business rules or input?

  • Validate the changes before modifying the model objects. Don't allow invalid model objects to exist in memory (from the perspective of the public API which your model exposes, and excluding multithreading scenarios).
  • Wrap your changes in a transaction, which will fail if validation of your model objects fails (why are you allowing invalid model objects in the first place?).
Justice
this application is a web application and the session is handled by the unit of work. It is flushed after each web request automatically.How do you propose I validate my business objects? The example given is trival, BUT, others are much harder to validate without creating an object.
Kyle West
Validate all data and operations at call-time so that the property cannot change to something invalid at all. The NHibernate metaphor is simple - a persistent object in memory and the data in the DB should be considered to be inseperable; change one == change the other. Ignore the plumbing.
DotNetGuy
+2  A: 

Specifically for ActiveRecord: if you don't change the SessionScope yourself, AR defaults to a session management pattern of session-per-call, where a new session is created for every operation of ActiveRecordMediator. Thus all objects you retrieve are already disconnected from their parent session once you retrieve them. Changes will not be persisted until / unless you call Save (or SaveAndUpdate, or even Update) which, in a session-per-call pattern, will create a session, attach the object you're saving to that session, call Save, and then dispose the session (causing a Flush and thus the writing of changes).

When you use AR this way, it does exactly what you seem to want (ie no changes are written back unless you explicitly call Save). But this explicitly goes against the expected NHibernate behaviour, and you can't do lazy loading or get much use out of caching. I have used this pattern for some Web apps, but they are designed up-front for eager loading and quite a lot of the persistent objects are effectively immutable and static and can be loaded en-masse at startup time and cached without a connection to any session. If your app doesn't suit this model then session-per-call is probably a bad idea.

Since you seem to be using a UOW pattern, you can't take advantage of this behaviour. Thus you must either evict the object from the NHibernate session (and getting access to the real ISession instance is actually not quite as easy as it seems in AR), or change the way your app works so that properties of persisted objects are not actually modified until after your business rules have been validated.

DotNetGuy
+1  A: 

So, I know this is completely overkill but here's how I fixed it.

Converted to NHibernate 2.0 and used the NHibernate Validator project to run my input validation rules. I haven't handled the business rules yet but I think I can do them with custom Validator rules, if that doesn't work I can use the nhibernate events.

Since I was already using the repository pattern the conversion was incredibly simple. It only took about 4-5 hours and our model is pretty extensive. Being able to generate the .xml files from the AR attributes was a huge time saver.

Kyle West