NHibernate Tips: Updating objects

3 04 2009

1. Updating in the same ISession

Transactional persistent instances (ie. objects loaded, saved, created or queried by the ISession) may be manipulated by the application and any changes to persistent state will be persisted when the ISession is flushed (discussed later in this chapter). So the most straightforward way to update the state of an object is to Load() it, and then manipulate it directly, while the ISession is open:

DomesticCat cat = (DomesticCat) sess.Load( typeof(Cat), 69L );
cat.Name = "PK";
sess.Flush();  // changes to cat are automatically detected and persisted

Sometimes this programming model is inefficient since it would require both an SQL SELECT (to load an object) and an SQL UPDATE (to persist its updated state) in the same session. Therefore NHibernate offers an alternate approach.

2. Updating detached objects

Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction. (Applications that use this kind of approach in a high-concurrency environment usually use versioned data to ensure transaction isolation.) This approach requires a slightly different programming model to the one described in the last section. NHibernate supports this model by providing the method Session.Update().

// in the first session
Cat cat = (Cat) firstSession.Load(typeof(Cat), catId);
Cat potentialMate = new Cat();
firstSession.Save(potentialMate);

// in a higher tier of the application
cat.Mate = potentialMate;

// later, in a new session
secondSession.Update(cat);  // update cat
secondSession.Update(mate); // update mate

If the Cat with identifier catId had already been loaded by secondSession when the application tried to update it, an exception would have been thrown.

The application should individually Update() transient instances reachable from the given transient instance if and only if it wants their state also updated. (Except for lifecycle objects, discussed later.)

Hibernate users have requested a general purpose method that either saves a transient instance by generating a new identifier or update the persistent state associated with its current identifier. The SaveOrUpdate() method now implements this functionality.

NHibernate distinguishes “new” (unsaved) instances from “existing” (saved or loaded in a previous session) instances by the value of their identifier (or version, or timestamp) property. The unsaved-value attribute of the <id> (or <version>, or <timestamp>) mapping specifies which values should be interpreted as representing a “new” instance.

<id name="Id" type="Int64" column="uid" unsaved-value="0">
    <generator class="hilo"/>
</id>

The allowed values of unsaved-value are:

  • any – always save
  • none – always update
  • null – save when identifier is null
  • valid identifier value – save when identifier is null or the given value
  • undefined – if set for version or timestamp, then identifier check is used

If unsaved-value is not specified for a class, NHibernate will attempt to guess it by creating an instance of the class using the no-argument constructor and reading the property value from the instance.

// in the first session
Cat cat = (Cat) firstSession.Load(typeof(Cat), catID);

// in a higher tier of the application
Cat mate = new Cat();
cat.Mate = mate;

// later, in a new session
secondSession.SaveOrUpdate(cat);   // update existing state (cat has a non-null id)
secondSession.SaveOrUpdate(mate);  // save the new instance (mate has a null id)

The usage and semantics of SaveOrUpdate() seems to be confusing for new users. Firstly, so long as you are not trying to use instances from one session in another new session, you should not need to use Update() or SaveOrUpdate(). Some whole applications will never use either of these methods.

Usually Update() or SaveOrUpdate() are used in the following scenario:

  • the application loads an object in the first session
  • the object is passed up to the UI tier
  • some modifications are made to the object
  • the object is passed back down to the business logic tier
  • the application persists these modifications by calling Update() in a second session

SaveOrUpdate() does the following:

  • if the object is already persistent in this session, do nothing
  • if the object has no identifier property, Save() it
  • if the object’s identifier matches the criteria specified by unsaved-value, Save() it
  • if the object is versioned (version or timestamp), then the version will take precedence to identifier check, unless the versions unsaved-value="undefined" (default value)
  • if another object associated with the session has the same identifier, throw an exception

The last case can be avoided by using SaveOrUpdateCopy(Object o). This method copies the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. The method returns the persistent instance. If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance. Otherwise, the given instance does not become associated with the session. In most applications with detached objects, you need both methods, SaveOrUpdate() and SaveOrUpdateCopy().

3. Reattaching detached objects

The Lock() method allows the application to reassociate an unmodified object with a new session.

//just reassociate:
sess.Lock(fritz, LockMode.None);
//do a version check, then reassociate:
sess.Lock(izi, LockMode.Read);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.Lock(pk, LockMode.Upgrade);

Actions

Information

Leave a comment