NHibernate: The Upsides and Downsides of Cascading and Not Lazyloading

I’m nearing the end of my first MVC project using NHibernate. It’s a powerful ORM with a ton a features, but it was hard to figure out how to implement simple, safe, and performance-friendly reads & writes to the database, which is funny, considering that’s what an ORM is supposed to do. I attribute this difficulty to NHibernate’s one obvious flaw: it has no official documentation. The two new features/extensions that we used the most, Fluent NHibernate and QueryOver, have no official resources, so learning was often a matter of both searching through Stack Overflow posts for answers and trial & error.

Cascade.SaveUpdate()

One NHibernate feature, Cascade.SaveUpdate, was very effective but also had dire consequences if used incorrectly. In the HR section of our application, we use two classes, Employee and User. User has properties like name, username, password, etc.–pretty much a standard system user class. Employee contains User as a property, as well as details like hired date, address, and emergency contact. We had a view with form fields that encompassed both Employee and User properties, so I decided to use the Employee class as the model for the view, and then use Cascade.SaveUpdate in the Employee map to persist changes to the User table, like this.

Employee class:


public class Employee { 
public Employee() { } 
public virtual int EmployeeId { get; set; } 
public virtual User User { get; set; } 
[Required(ErrorMessage = "Hire date is required.")] 
public virtual DateTime? HiredDate { get; set; } 
public virtual DateTime? TerminatedDate { get; set; } 
public virtual bool? IsAttorney { get; set; } 
public virtual DateTime? ModifiedDate { get; set; } 
public virtual DateTime? CreatedDate { get; set; } 
public virtual IList<Rate> Rates { get; set; } 
//etc.
}

Employee map:


public EmployeeMap() { 
Table("employee"); 
LazyLoad(); 
Id(x => x.EmployeeId).GeneratedBy.Identity().Column("employee_id"); 
References(x => x.User).Column("user_id").Not.LazyLoad().Cascade.SaveUpdate(); 
Map(x => x.HiredDate).Column("hired_date"); 
Map(x => x.IsAttorney).Column("is_attorney"); 
Map(x => x.ModifiedDate).Column("modified_date"); 
Map(x => x.CreatedDate).Column("created_date"); 
HasMany(x => x.Rates).KeyColumn("employee_id").Not.LazyLoad();
//etc.
} 

Saving data to both the Employee and User tables was then very simple: just do an update with your NHibernate database session. It’s very important, though, that you think through every use of Cascade.SaveUpdate. In our application, one of the developers (it could have been me) copied and pasted a property from a map that contained Cascade.SaveUpdate into another map. Later on in QA, we experienced a bug where Trades in the system were losing their respective Clients. This was not good; it broke one of the most critical links for database integrity. We discovered it was a result of that incorrectly copied Cascade.SaveUpdate. Changes were cascading on our Client entity in an area of the application where they should not have been, and as a result NHibernate was doing rogue updates to the database, including nulling out foreign keys for Client in our Trades table. (As a side note, this foreign key field should have been set to not null, which we then corrected.)

When determining if you should use a cascade, at least in terms of an MVC application, you need to look at the associated view for that class. Do you actually need to persist changes for that class, based on your view? Is there any chance that the object will be instantiated, but it will contain no actual data? This happened to us as well. We had a ContactInfo class, which held details for clients like phone number and email, but it was not required in our form. If no contact details were entered, NHibernate would save a row in the associated table with all null values, and soon we had hundreds of null rows clogging up that table.

Not.LazyLoad()

In the Employee map above, you can see that I have several properties set to Not.LazyLoad, including User. If I didn’t have User set to Not.LazyLoad, I would get a lazyload exception when I pull the Employee from the database and try to access the properties of User in my view. Using Not.LazyLoad makes sense here; whenever we select an Employee, we also want its associated User details. However, an important caveat is that NHibernate pulls properties with Not.LazyLoad using separate select statements. Normally you would write a SQL statement that had an inner join between the Employee and User tables, but NHibernate performs one select from Employee, and then another Select from User.

This is not a big deal in our example, but it quickly becomes a problem when you retrieve a collection of entities that have many nested properties with Not.LazyLoad. We had this occur on a view that provides the list of active Clients in the application. When I opened SQL Profiler and ran the NHibernate query that pulled back the collection of Clients, I saw over a hundred select statements! Again, this was because multiple nested classes under Client had properties with Not.LazyLoad set in their maps.

In order to avoid all of the separate selects and simulate the proper SQL join, I ended up using QueryOver. Again, this is where NHibernate gets weird, because there are several options for querying (QueryOver, Criteria, HQL), and not a single option is officially recommended or documented. QueryOver is nice because it very LINQ-like and, as another developer on the project put it, doesn’t litter the project with “magic strings.” The query for Employee plus its User in QueryOver looks like this.


IEnumerable<Employee> employees;

//establish a session using your factory manager class and wrap it in a using statement
using (ISession session = NHibernateSessionFactoryManager.Factory.OpenSession())
{
User userAlias = null;
employees = session.QueryOver<Employee>()
.JoinAlias(e => e.User, () => userAlias)
.List();
}

//do stuff with employees, like return it as a model

In the example above, I use JoinAlias to specify an inner join from Employee to User. This brings up a final import note on QueryOver. If you have a property in a map with Not.LazyLoad, and you do not provide a JoinAlias for it in your query, NHibernate will perform a separate select. Taking the query above as an example, say my User class has a nested class of its own, PasswordHistory. If, in the User map, I place a Not.LazyLoad on the LoginHistory property, NHibernate will perform a separate select for it. In profiler, you will end up seeing two select statements: one for the Employee with a join to User, and another select from the LoginHistory table for the specific User. To combine all of this into one select statement, you would do the following.

User userAlias = null;
LoginHistory lhAlias = null;
employees = session.QueryOver<Employee>()
.JoinAlias(e => e.User, () => userAlias)
.JoinAlias(e => e.User.LoginHistory, () => lhAlias)
.List();

In summary, I have mixed feelings about NHibernate. I would probably use it on another application, but that’s mainly because I have invested the time into understanding its features. If I were new to all of the major (non-micro) ORMs for .NET, I might choose Entity Framework instead, because it has official documentation and it is not open source like NHibernate. But that is a subject for a different post.

Advertisements
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: