Transitive persistence/cascading in action

In this section, we are going to look at a select few persistence situations to explore the transitive persistence and cascading in detail. We will not be able to explore each and every possible scenario, so we are going to be looking at the following few and understand in detail how these play out under the hood. Understanding these examples should give you enough knowledge to understand any code for any complex scenario:

  • Saving a transient entity with association to one or more other transient entities
  • Updating a persistent entity with one or more associations added/removed
  • Deleting a persistent entity with one or more associations set

Let's get started then.

Saving a transient entity with association to other transient entities

We have seen examples of saving an entity graph involving all transient entities in Chapter 3, Let's Tell NHibernate About Our Database, while discussing association mappings. But let's look at the examples again and understand what happens under the hood. In the following example, we try to save a transient instance of employee having some transient benefit instances added to its Benefits collection:

object id = 0;
using (var transaction = Session.BeginTransaction())
{
  id = Session.Save(new Employee
  {
    Benefits = new HashSet<Benefit>
    {
      new SeasonTicketLoan
      {
        Amount = 1200,
        MonthlyInstalment = 100,
        StartDate = new DateTime(2014, 5, 12),
        EndDate = new DateTime(2015, 4, 12)
      }

    }
  });
  transaction.Commit();
}

When the preceding code is run, NHibernate first saves the employee entity. After that, NHibernate inspects the associations and checks the cascade setting on the association mapping to determine what needs to be done to the associated benefit entity. If we had set cascade to none, then NHibernate would ignore the transient benefit entity and commit the transaction. On the other hand, had the cascade been set to save-update or all, NHibernate would generate necessary insert statements to insert the benefit records in database. Following SQL statements would be sent to database by NHibernate in that situation:

Saving a transient entity with association to other transient entities

If you notice, NHibernate has generated an extraneous update statement to set correct foreign key value in the Employee_Id column of the Benefit table. This looks a bit unwise on part of NHibernate but fortunately there is way to fix this. In one of the upcoming sections, we will look at why NHibernate generates this extraneous update statement and how to fix this.

Another thing to note here is that when the record is inserted into the Benefit table, Employee_Id is inserted as null. This is because we did not correctly set the Employee property on the SeasonTicketLoan instance, leaving it as null. In the next section, we will talk about why it is important to set both ends of a bidirectional association.

A word on bidirectional associations

An association between two database records is usually bidirectional. When two database tables are associated using a foreign key, then you are able to navigate from one to other in any direction via different SQL joins. The same is not true for the entities that map to these database tables. In C#, you can choose to restrict the navigation to go from one end of the association to the other but not the other way round. Or like in case of Employee to Benefit mapping in our domain model, you can choose to make them bidirectional by having a property of other type on each side.

Because bidirectional associations have navigations from both ends, additional responsibility of setting both ends has to be handled by someone. In some cases, NHibernate takes care of that. You set only one end of the association and NHibernate ensures that both ends of the association are set when entity is saved in the database. But this is not guaranteed to work all the time and there always are edge-cases where this would fail. So, it is recommended that we always set both ends of a bidirectional association. Another reason why we should always set both ends of a bidirectional relation is that we are creating a graph of .NET objects in the memory. We may use these objects in the context of NHibernate or we may use these just like plain .NET objects. If there is a valid bidirectional relation present between two objects, then it is always safer to set it than leave it for someone else to update that relation. You never know if some piece of code tries to navigate that relation even before NHibernate has a chance to update it properly. Following is how the preceding code listing would look when this is implemented:

object id = 0;
using (var transaction = Session.BeginTransaction())
{
  var seasonTicketLoan = new SeasonTicketLoan
  {
    Amount = 1200,
    MonthlyInstalment = 100,
    StartDate = new DateTime(2014, 5, 12),
    EndDate = new DateTime(2015, 4, 12)
  };
  var employee = new Employee
  {

    Benefits = new HashSet<Benefit>
    {
      seasonTicketLoan
    }
  };
  seasonTicketLoan.Employee = employee;

  id = Session.Save(employee);
  transaction.Commit();
}

Note that we instantiated the Employee and SeasonTicketLoan instances independently. We then added the SeasonTicketLoan instance to the Benefits collection on Employee and set the Employee property on the SeasonTicketLoan instance to the Employee instance. This code does the job but is not perfectly reusable. Every time a benefit is added to an employee, we need to remember to set both ends of the association. It would be hard to trace subtle bugs that may arise out of forgetting to do this. This can be avoided by defining a method on the Employee class and making sure that only this method is used when a benefit needs to be added to an employee, as shown next:

public virtual void AddBenefit(Benefit benefit)
{
  benefit.Employee = this;
  Benefits.Add(benefit);
}

This method makes sure that the Employee property at the benefit end is set correctly every time and also adds the passed benefit instance to the Benefits collection. Note that we are not checking if the Benefits collection is initialized or not. You can either add a check for that and initialize the collection if not initialized already or add the following default constructor to make sure that the Benefits collection is always initialized:

public Employee()
{
  Benefits = new HashSet<Benefit>();
}

Same logic applies to bidirectional association between Employee and Address. You can add a method similar to the previous one that lets you set the ResidentialAddress property on the Employee class or just change the property to look as follows:

private Address residentialAddress;
public virtual Address ResidentialAddress
{
  get
  {
    return residentialAddress;
  }
  set
  {
    residentialAddress = value;
    if (value != null) 
      residentialAddress.Employee = this;
  }
}

Here, we have introduced a backing field for the property. From the setter of the property, we also set the Employee property on the backing field to this which is an employee instance. Now, you can set the ResidentialAddress property in the usual way and both ends of the association are guaranteed to be set.

Updating associations on a persistent entity

At the beginning of this section, we have seen the example of updating simple properties on a persistent entity, so I will not repeat that here. We have just looked at an example of adding entities to a collection on a transient entity. Adding entities to a collection on persistent entity is no different. The only thing you need to ensure is that both ends of the association are set if there is a bidirectional association. Removing items from a one-to-many association on a persistent entity works slightly differently and that is what we are going to discuss here.

Previously, when we discussed various cascade options, we learned that all-delete-orphan should be our default cascade style for one-to-many associations. Here, we would see with example what difference does that option bring in contrast to something such as save-update or delete. In the following code listing, a benefit instance of type Leave is removed from the Benefits collection on employee:

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

  var leave = employee.Benefits.OfType<Leave>().FirstOrDefault();
  emp.RemoveBenefit(leave);

  tx.Commit();
}

We have a used convenience method named RemoveBenefit defined on the Employee class in order to remove the benefit instance from collection. This is in line with discussion around setting both ends of a bidirectional association. The same logic applies when breaking a bidirectional association. In this case, just removing the benefit from the Benefits collection of Employee is not enough. We also need to set the Employee property on Benefit to null.

Note

In the next section, we will look at a setting called inverse which affects how NHibernate persists associations. Inverse also drives the need to make sure that removal action is carried out at the correct end of association. But as a general guiding principal, we would ensure that association is broken from both ends. And to make it fool-proof, we would use convenience methods such as RemoveBenefit.

Following is how the RemoveBenefit method looks:

public virtual void RemoveBenefit(Benefit benefit)
{
  benefit.Employee = null;
  Benefits.Remove(benefit);
}

If cascade setting on mapping of the Benefits collection is set to save-update or all, then the preceding code would result in following SQL being generated:

  UPDATE
    Benefit
  SET
    Name = @p0,
    Description = @p1,
    Employee_Id = @p2
  WHERE
    Id = @p3;
  @p0 = NULL [Type: String (0)],
  @p1 = NULL [Type: String (0)],
  @p2 = NULL [Type: Int32 (0)],
  @p3 = 65537 [Type: Int32 (0)]

Note that, though we asked NHibernate to remove leave instance, NHibernate only updated the value of foreign key column Employee_Id in the Benefit table to NULL. It effectively only deleted the association between employee and benefit but the benefit instance is still there in the database. Such behavior is desired if there are other records in database referring to this benefit record or for some reason you are not allowed to delete the so called orphan records not referenced anymore. But what if you want these orphan records to be deleted when no other database record references this record? That is when you use cascade setting delete-orphan which tells NHibernate that the moment it detects an orphan record resulting out of any database operation, it should delete it. Following code listing shows how you can set cascade value of delete-orphan using mapping by code:

Set(e => e.Benefits, mapper =>
{
  mapper.Key(k => k.Column("Employee_Id"));
  mapper.Cascade(Cascade.All.Include(Cascade.DeleteOrphans));
  mapper.Inverse(true);
},
relation => relation.OneToMany(mapping => mapping.Class(typeof(Benefit))));

Note that we have set the cascade value to All first and then included DeleteOrphan on top. This is because cascade values work in a similar way to a set or union. You are telling NHibernate which set of operations you want to cascade by combining them as we did in the preceding example. After setting the cascade value to all-delete-orphan, following SQL is generated by NHibernate for the same piece of code:

  DELETE
  FROM
    Leave
  WHERE
    Id = @p0;
  @p0 = 65537 [Type: Int32 (0)]

  DELETE
  FROM
    Benefit
  WHERE
    Id = @p0;
  @p0 = 65537 [Type: Int32 (0)]

You can see that NHibernate has detected an orphan record and issued the commands to delete it. It also figures out that record from the Leave table needs to be deleted before it can delete the record in the Benefit table.

Deleting a persistent entity with one or more associations set

In more than one way, the API for deleting a persistent entity is similar to the API for saving an entity. Similar to Save(), session has a method named Delete() to which you pass the entity instance that you want to delete. The way NHibernate navigates through the associations and checks cascade settings while saving the entities, it does the same thing while deleting the entities. Following code listing deletes an employee instance:

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

If the preceding employee instance has all its associations set with a cascade value that would propagate the delete operation (delete or all), then NHibernate would navigate all the associations and issue delete commands in correct order to ensure that all the entities are deleted without any violation of dependency.

After the entity is deleted from the database, NHibernate also removes it from the session.

Note

The code listings used previously is taken from the unit tests that I wrote for this section. To keep the text brief, I have only included the relevant lines of code from the different tests. Feel free to look at the full tests by downloading the source code of the chapter.

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

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