Transitive persistence using cascade styles

NHibernate implements transitive persistence wherein updates made to a persistent entity are automatically synchronized to database on commit of the transaction. Let's look at a very simple example of how this works. In the following unit test, we save a transient employee instance, thus making it persistent. We then load the persistent instance from database inside a new transaction, update the FirstName and LastName property, and commit the transaction. Lastly, we clear the session and load the same entity instance from database and confirm that FirstName and LastName is updated correctly.

[Test]
public void UpdateEmployee()
{
  object id = 0;
  using (var tx = Session.BeginTransaction())
  {
    id = Session.Save(new Employee
    {
      Firstname = "John",
      Lastname = "Smith"
    });

    tx.Commit();
  }

  Session.Clear();

  using (var tx = Session.BeginTransaction())
  {
    var emp = Session.Get<Employee>(id);

    emp.Firstname = "Hillary";
    emp.Lastname = "Gamble";

    tx.Commit();
  }

  Session.Clear();

  using (var tx = Session.BeginTransaction())
  {
    var employee = Session.Get<Employee>(id);
    Assert.That(employee.Firstname, Is.EqualTo("Hillary"));
    Assert.That(employee.Lastname, Is.EqualTo("Gamble"));
    tx.Commit();
  }
}

Note

We placed a call to ISession.Clear() in the preceding test. This is done in order to clear everything held in the identity map of the session so that we can have a fresh session. We need to do this because we are using the in-memory SQLite database which is destroyed the moment session is closed. A new instance of in-memory database would be created next time we open a new session. So all the data we saved in the previous instance would be lost. In order to keep that database instance around, we are clearing the session. There are other ways of fixing this issue. For instance, we could have used a file-based SQLite instance instead of in-memory one. Or we could have just evicted the entity instances that we do not want to have in the session for our tests to work. Both of these options are not as easy and straightforward to use than clearing the session. But at the same time, remember that ISession.Clear should be used with extreme care. It should be avoided in production code unless you know what you are doing. ISession.Clear not only clears the objects in the identity map but it also removes any objects queued for inserts/updates to database.

In the preceding code, we did not call any method on session object to tell NHibernate that we have updated few properties on the employee instance. When the preceding code is run, NHibernate would send the following SQL to database on commit of transaction:

  UPDATE
    Employee
  SET
    EmployeeNumber = @p0,
    Firstname = @p1,
    Lastname = @p2,
    EmailAddress = @p3,
    DateOfBirth = @p4,
    DateOfJoining = @p5,
    IsAdmin = @p6,
    Password = @p7
  WHERE
    Id = @p8;
  @p0 = NULL [Type: String (0)],
  @p1 = 'Hillary' [Type: String (0)],
  @p2 = 'Gamble' [Type: String (0)],
  @p3 = NULL [Type: String (0)],
  @p4 = '01/01/0001 00:00:00' [Type: DateTime (0)],
  @p5 = '01/01/0001 00:00:00' [Type: DateTime (0)],
  @p6 = False [Type: Boolean (0)],
  @p7 = NULL [Type: String (0)],
  @p8 = 11 [Type: Int32 (0)]

Tip

The SQL generated to update the employee record updates all the columns of the Employee table though only the FirstName and LastName properties were updated. This is NHibernate's default behavior. A property called dynamic-update controls this behavior. This property is configured through mapping at entity level. When set to true, NHibernate only includes the changed properties in the generated update statement. There are some pros and cons of dynamic updates which are left for the readers to explore. Similar to dynamic updates, another feature, dynamic insert offers behavior on the same lines around inserts.

The way transitive persistence works internally is through a feature called dirty checking. When session is flushed (or transaction is committed), NHibernate checks for all entities present in its identity map to see if there is any entity which is newly added, deleted, or changed. For the changed entities, NHibernate checks the current state of the entity with the one that was loaded from the database and still held within identity map. All changed entities are marked as dirty and NHibernate generates appropriate SQL to synchronize the changes to the database.

The example we just saw only updated primitive properties on a single entity. What about updating an entity having associations to other entities? This is a most common persistence requirement and NHibernate has taken care of it through cascading the save/update/delete operations on parent entity to the associated child entities. Not only that, but NHibernate gives you total control over this cascading operation and lets you choose which actions of save, update, and delete on parent entity are cascaded down to the associated child entities. The Cascade option on association mapping is used to declare this. We have briefly looked at the cascade option in Chapter 3, Let's Tell NHibernate About Our Database, during discussion of mapping associations. Let's dig deeper into cascade now.

Cascade setting on association mapping lets you specify how NHibernate should cascade a particular action performed on parent object to its associated objects. For instance, if you have a transient instance of the Employee entity referencing an instance of the Address entity and the Employee entity is saved in the database, would you like the associated Address entity to be saved in database as well? A cascade setting of none would leave the associated entity as is but a cascade setting of save-update would save the Address entity in database. There are several cascade options defined but following are most commonly used options:

  • none: Do not cascade any actions from parent entity to associated child entities. This is NHibernate's default when no cascade option is specified on association mapping.
  • save-update: When an entity is passed to the SaveOrUpdate method, NHibernate navigates the associations from passed entity to other entities. If any of the associations are mapped with the cascade option of save-update, NHibernate would save or update the associated entity (internally, NHibernate would pass associated entity to the SaveOrUpdate method).
  • delete: When an entity is deleted using the Delete method and any associations from this entity to other entities have the cascade option set to delete, then those associated entities are also deleted.
  • delete-orphan: This setting is used to handle a special situation. When a child entity is dereferenced from its parent entity then all that NHibernate does by default is to set the foreign key on child entity's table to null, so that association between parent and child records in the database does not exist anymore. Child records like these are sometimes called orphan records as they do not have any meaning without being associated to their parent records. If you want NHibernate to additionally delete the orphan child record as well, then setting cascade to delete-orphan would do the job. Note though that this setting has nothing to do with the delete setting.
  • all: This option cascades any operation performed on an entity to its associated child entities.
  • all-delete-orphan: This is just a union of all and delete-orphan. With this cascade option specified, NHibernate would cascade any action from parent entity to child entity and in addition, it would also delete any orphan entities.

The other cascade options are persist, refresh, remove, and detach. Each of these correspond to a method on ISession with same name. As with most NHibernate features, a question may have come to your mind – how to decide which cascade style to use? There is no black and white answer. If there was one, NHibernate would not have given us these many options, isn't it? But there fortunately are some guidelines that you can follow:

  • Always use all-delete-orphan as your default choice. This would cascade all operation down to every association so you do not have to think about associations once they are mapped correctly. This option would also ensure that there are no orphan records lying around the database. In the next section, we would explore this in more detail.
  • In some legacy databases, you may not be allowed to delete existing database records. In such situations, save-update is your best friend.
  • If your domain model involves self-referencing bidirectional associations (associations from an entity to itself), then you may want to choose none as cascade option on one of the associations. This would prevent NHibernate from navigating the whole object graph in memory leading to bad performance.

Cascading is such an important concept that just giving a brief description of each cascade style does not do enough justice to it. So next we would look at some examples of persistence situation where cascading along with transitive persistence do their magic, while we write very little code to synchronize to the database the changes made to entities.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.187.165