Saving entities

As with previous chapter, we will begin by going through a piece of code we had used in Chapter 3, Let's Tell NHibernate About Our Database. If you can remember, we had used the following code to save an instance of the Employee class in the database:

object id = 0;
using (var transaction = Session.BeginTransaction())
{
  id = Session.Save(new Employee
  {
    EmployeeNumber = "5987123",
    Firstname = "Hillary",
    Lastname = "Gamble",
    EmailAddress = "[email protected]",
    DateOfBirth = new DateTime(1980, 4, 23),
    DateOfJoining = new DateTime(2010, 7, 12),
    IsAdmin = true,
    Password = "Password"
  });
  transaction.Commit();
}

We started with call to Session.BeginTransaction which signaled NHibernate to begin a fresh transaction for us. We then created a new instance of the Employee class, set required properties on it, and passed it into the Save method on session. The Save method on the ISession interface is one of the persistence APIs offered by NHibernate. The Save method is used to ask NHibernate to track an in-memory instance of an entity for addition into a database table. You should use the Save method when you know for sure that entity does not exist in database or in identity map of session.

Note

An identity map ensures that each entity instance is loaded into the memory only once. You can think of identity map as a hashtable where identifier value of the entity instance is used as key. You can read more about identity map pattern in Martin Fowler's article at http://martinfowler.com/eaaCatalog/identityMap.html. Session internally maintains an identity map or cache to store all the persistent entities that are added to session through the Save method or are loaded by session from the database via different querying methods. NHibernate makes significant use of identity map to avoid having to load the same entity instance more than once in the same session.

Also note that we created the employee instance right where we passed it into the Save method. But you can create it in a separate statement. This separate statement can even be placed outside of the using statement block.

The Save method returns a parameter of type object. This is the identifier for the entity being saved. The identifier property can be one of the many supported types and not just int. There is no way of knowing this at compile time, hence the Save method returns object. Note that the employee instance is still not saved in the database. The identifier value is generated by NHibernate using the identifier generation strategy we specified in the mappings for the Employee class.

We then call the Commit method on transaction. It is at this point that NHibernate generates appropriate set of SQL statements to insert new record in the Employee table and sends them to database for execution.

Note

Some identifier generation strategies also play a role in determining when entities are actually stored in the database. Strategies such as identity and sequence, which depend on the database to generate identifier value, would result in the entity being stored in the database as soon as ISession.Save is called. NHibernate assigns the identifier value to a fresh entity when it is passed to the Save method. In case of strategies such as identity and sequence, identifier value can be obtained only after inserting the database record. This is why NHibernate would store the entity in database immediately after call to ISession.Save.

The Employee instance is persisted at this point. If we hold this instance in a local variable, close the session, and retain the local variable even after the session is closed, then the instance becomes detached. This is mainly because the instance is not associated with any session. If the instance is associated with some other session then entity would still be persistent relative to that session. Since detached entities are not associated with any session, changes made to them cannot be tracked by NHibernate. However, if the same entity is persistent relative to some other session then that session would track any changes made to the entity. Entity state can be changed to detached while keeping the session still open by passing the entity to the Evict() method on ISession. Calling the Evict() method would remove the entity from session, thus making it detached.

Following is the sequence of SQL statements generated by the preceding piece of code. Note that we are using hilo identifier generation strategy here.

  select
    next_hi
  from
    hibernate_unique_key

  update
    hibernate_unique_key
  set
    next_hi = @p0
  where
    next_hi = @p1;
  @p0 = 2 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]

  INSERT
  INTO
    Employee
      (EmployeeNumber, Firstname, Lastname, EmailAddress, DateOfBirth, DateOfJoining, IsAdmin, Password, Id)
  VALUES
      (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8);
  @p0 = '5987123' [Type: String (0)],
  @p1 = 'Hillary' [Type: String (0)],
  @p2 = 'Gamble' [Type: String (0)],
  @p3 = '[email protected]' [Type: String (0)],
  @p4 = '23/04/1980 00:00:00' [Type: DateTime (0)],
  @p5 = '12/07/2010 00:00:00' [Type: DateTime (0)],
  @p6 = True [Type: Boolean (0)],
  @p7 = 'Password' [Type: String (0)],
  @p8 = 32768 [Type: Int32 (0)]

Let's try to understand what SQL statements NHibernate has generated for us. Preceding statements can be divided into two parts. The first part is all about identifier generation. Since we have configured to use hilo as identifier generation strategy, NHibernate is making itself ready with all the data it needs in order to generate identifiers using hilo algorithm. What I mean by that is – hilo needs a set of two numbers to work with. One is hi which is sourced from a database table and other is lo which is calculated by NHibernate. NHibernate combines these two numbers using a formula to generate a unique number that can be used as identifier. The way this works is, for generating lo value NHibernate uses range defined by the max_lo parameter that can be specified during mapping of identifier column in question. If max_lo configured is 10 then NHibernate starts generating identifier numbers at (max_lo * initial hi value) + 1. Usually the initial hi value coming out of database is 1, so the first number becomes 11. Next time an INSERT operation needs an identifier, NHibernate increments the last value and returns 12. Every time an identifier value is returned, NHibernate also checks if it has gone over the range defined by max_lo value, which in our case is 10. So after returning 20, NHibernate knows that it has gone over the range and gets the next hi value from database, which is 2. It runs the same algorithm again to return numbers from 21 to 30 this time, and so on.

If you do not specify max_lo in mapping then NHibernate assumes a default of Int16.MaxValue which is 32767. This is what we see in the preceding example. NHibernate keeps generating unique identifier values without hitting the database till it runs out of the max_lo value range. Using appropriate max_lo and hi values, you can ensure that NHibernate generates unique identifier values without hitting the database too much. There are two times when NHibernate needs a trip to database when using this strategy:

  • When a new session factory is built, NHibernate fetches the next hi value from the database and retains it. At the same time, NHibernate also updates the next hi value in database by incrementing the value that it just fetched. This is what we see in the previous example.
  • When session factory runs out of the max_lo value range. At this point, NHibernate resets the max_lo value and fetches the next hi value from database again.

Being able to generate unique identifier without having to hit the database is biggest strength of hilo algorithm. Besides that, the identifiers values are integers which work well with indexing of the Id column, which is also primary key of the table. On the flip side, a low value of max_lo would mean that NHibernate would run out of max_lo range frequently, leading to more database hits than expected. On the other hand, a very high value of max_lo would lead to sparse identifier values if session factory is recreated before the max_lo range is exhausted. You should choose the ideal max_lo value after carefully studying the application's runtime behavior and the environment in which the application is running.

The next statement in the previous SQL is a simple insert into the Employee table. Note that the value used for the Id column is what NHibernate calculated using hi and lo values.

The example we looked at is quite simple. It involved a single transient entity without any associations to the other entities. Next, we will look into complex scenarios and see what NHibernate offers to deal with these scenarios.

Saving entities – complex scenarios

Persistence, as we saw in the previous example, is simple because NHibernate does not need to do much work like building multiple SQL insert/update statements and executing them in the right sequence at appropriate time. But persistence requirements of real world software are usually more complex than this. In real life, we may encounter one or more of the following situations:

  • A transient entity with associations to one or more other transient entities needs to be persisted
  • A transient entity with one or more associations to other persistent entities needs to be persisted
  • One or more properties of a persistent entity are modified by application that needs to be synchronized to database
  • One or more associations are added/removed from a persistent entity
  • A persistent entity needs to be deleted
  • Updates are made to detached entities

We have seen that session has the Save method that can be used to save a transient entity instance into database. Other than Save, session has the following methods available to deal with different situations:

  • Update: This is used to persist the changes made to a detached entity.
  • Delete: This is used to delete a persistent entity (as a side effect of this, the in-memory copy of the entity becomes transient).
  • SaveOrUpdate: This is used to let NHibernate figure out whether the entity being passed is transient or persistent and action it accordingly.
  • Merge: This is used to persist changes made to detached entity. This may sound confusing with Update but there is a subtle difference that we would look at in a moment.
  • Refresh: This is used to synchronize the latest state of an entity from database into the memory. This method is typically used in scenarios where database has triggers which alter the state of an entity (or add more details) after it has been saved. By calling the Refresh method after call to the Save method, any changes made by the database trigger would be synchronized with the entity instance.
  • Persist: This is similar to Save with some differences in how it works internally. Persist saves the entity instance into the database only if it is invoked inside of a transaction, whereas Save does not need to be invoked inside a transaction. Persist assigns the identifier value when the changes are actually flushed to the database as against Save which would assign the identifier value immediately.

Refresh and Persist are introduced here so that readers are aware that these methods are available. We will not go into details of these methods as they are used in very specific situations. For instance, the Persist method is used alongside long-running transactions where a business transaction spans beyond a single user interaction. Let's explore the other methods in more detail. We would start with Delete as other three have a bit of a similarity and should be looked at together. If you want to delete a persistent entity then you can pass it to the Delete method. When transaction is committed (or session is flushed), NHibernate would issue appropriate SQL statements to delete the entity from database and also remove it from the session. Following is how you would do it:

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

At this point, the entity becomes transient as it is still present in the application memory but is not present in any NHibernate session. Delete has some subtleties around it which we will cover in an upcoming section.

The Update method is useful when you have made changes to a detached entity and you want them to be synchronized to database. NHibernate automatically tracks any changes made to persistent entities. This capability is built into session and works as long as the entity is associated with a session. But detached entities are not associated with any session so any changes you make to such an entity are not tracked by NHibernate. Hence, it is not possible to detect and commit changes made to detached entities when transaction is committed. Instead, you need to explicitly tell NHibernate that you have updated this entity while it was disconnected from the session and the new session should treat it as such. Let's look at how it works:

var emp = //loaded from previous session which is now closed
using (var tx = Session.BeginTransaction())
{

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

  Session.Update(emp);

  tx.Commit();
}

Note that the Update method is provided to be used in a situation when you are sure that an entity which was previously loaded from database is no more present in the session cache. One way you can be in this situation is when session that loaded the entity is closed. Another way this can happen is when an entity is removed from session by calling the Evict method on session.

What if the detached employee instance is already present in the new session? In that case, calling the Update method would result in an exception. The Merge() method is provided to handle this situation. If you know that a particular detached entity is present in a session that you want to use to synchronize the detached entity, then you first pass the detached entity to the Merge() method. NHibernate would then update the entity instance in its identity map with the one passed into the Merge() method. Any changes detected during this update and any changes made to the entity after that point are all synchronized to database when transaction is committed.

A caveat you should keep in mind while working with the Merge method is that the Merge method returns the merged entity which may be an entirely different instance than the one that was passed into the Merge method. After passing an entity to the Merge method, your code should work with the instance returned by the other call to Merge than the original instance. Let's take a look at the following code snippet to understand this better:

[Test]
public void MergeReturnsNewEntityInstance()
{
  object id = 0;

  //This is out first employee instance
  var employee1 = new Employee
  {
    Firstname = "John",
    Lastname = "Smith"
  };

  //Lets first save it so that we have it in session
  using (var tx = Session.BeginTransaction())
  {
    id = Session.Save(employee1);

    tx.Commit();
  }

  //Let's create another instance of Employee with same id
  var employee2 = new Employee
  {
    Id = (int) id
  };

  //Let's merge this new entity with session
  var employee3 = Session.Merge(employee2);
  
  //Let's confirm that employee2 and employee3 are not the same
  Assert.That(Object.ReferenceEquals(employee2, employee3), Is.False);

  //Let's assert that employee1 and employee3 are same instances
  Assert.That(Object.ReferenceEquals(employee1, employee3), Is.True);

}

The code has comments that explain most part of the code. The important part is that employee1 is the persistence instance which is present in the identity map of session. When detached instance employee2 is passed to the Merge method, it returns the same employee1 instance that is present in the identity map already. But employee2 is not the same as employee3, the instance returned by the Merge method.

To some extent, these methods help to make real life persistence situations we just described, easier to deal with. But there still remains a level of complexity that developer has to handle manually. Imagine a situation when you need to save an employee instance with all three types of benefit instances associated, a residential address present, and employee being part of few communities. You would have to first persist each individual entity independently and then update the persistent employee entity with associations to other persistent entities set. Besides being complex, imagine the number of lines of code you would end up writing for every such situation.

Fortunately, NHibernate makes it very easy to deal with real life persistence situations similar to ones we discussed previously and many more. NHibernate achieves this through an intricate implementation of something called transitive persistence. On top of that, NHibernate, through its implementation of cascade styles, offers the users fine grained control over how different entities in an object graph are persisted.

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

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