Using NHibernate as an ORM Solution for .NET, Page 6
Note the use of the session.SaveOrUpdate() method. In NHibernate, saving an object is the same as inserting into the database. Updating an object is the same as updating the database. Using the session.SaveOrUpdate() method frees the programmer from having to make the distinction of whether to save or update the object (though they can use the session.Save() or session.Update() methods if they choose).
If you refer to the ID tag in the mapping file for the Customer class, you will see the unsaved-value="null" attribute. (Note that if you have an ID that is not a nullable integer, you can set the unsaved-value to 0.) This means that if the object id is null, it has not been saved and a save (insert) needs to be performed. Otherwise, if the ID has a value, the object has previously been saved to the database and needs to be updated instead.
Also, this save/update method shows how to begin and commit a database transaction. Within the NHibernate save/update/delete logic, several queries may be performed in a single save/update/delete call to an object if you have an object mapped to multiple tables. It is important when saving/updating/deleting objects to use transactions to avoid making incomplete updates to the database that leave the database in an invalid state. Transactions are also useful for use in larger units of work that need to be performed together to ensure data consistency.
Deleting an object it is much the same as saving/updating an object. Just use the session.Delete(Object obj) method.
When you retrieve an object like the Customer object that has a collection of ProductOrders, or if you retrieve an object for a class that references another object, you might wonder when the collection or referenced object gets loaded. For example, during a standard load action where the Customer object is loaded from the database, two queries are needed. One query is needed to load the customer information from the Customer table and one query is needed to load the orders for this customer from the ProductOrder table. It seems inefficient that the second query to retrieve the orders would be run in every case, especially if you normally just want to look at the customer information. This is where lazy loading comes in.
NHibernate is smart enough to retrieve only a collection or referenced object when it is actually read somewhere in the code. In the case of the Customer object, when it loads the data into the Customer object, it loads only a proxy collection class for the orders. Using this proxy collection class, the query to retrieve the data for the orders is not run right away. The proxy collection class only runs the query to retrieve the collection data if a call is made to the collection to read the data.
Lazy loading works only if the session used to load the object is still active. As discussed earlier in this article, this is one of the reasons why the session per request model is used for session management. By using the session per request model, the session is left open for the duration of the request. This allows lazy loads at any time during the request.
If the session per request model is not feasible or perhaps another need arises, lazy loading is optional and can be turned off by adding the lazy-loading="false" mapping attribute to either the collection or the entire class. If lazy loading is not being used, the entire object will be loaded at the point of the session.Load() call.
Collection cascading is another subject of interest. It is a useful NHibernate feature that allows the collection object to be persisted automatically from a parent object. Cascading is turned off by default; this means that each collection object would need to be saved independently to be added to the collection of the parent object.
In this example application, cascading is turned on for the AssignedCustomers collection in the SalesPerson class. If you refer to the mapping file setup earlier in this article, the cascade attribute of the AssignedCustomers set is set to "save-update." This means that if you have a SalesPerson object and you add a new Customer object that has not been added to the system before, you can save the SalesPerson object and it will automatically save the Customer object.
NHibernate Performance Notes
In this article, you have gone through a lot of material on NHibernate and some examples of how to utilize it. I would like to finish up by briefly touching on the subject of NHibernate performance.
Using NHibernate may relieve the need to worry about writing the SQL for an application, but in doing so it can hinder a developer's ability of to tightly control the SQL being performed for an application. If the underlying SQL being run by NHibernate is not watched carefully, there are situations where undesired and/or un-optimized queries may be performed, especially in the case of collection handling. NHibernate also uses a good deal of reflection and does not use stored procedures which may cause a bit of a performance hit on its own.
These performance issues are a concern, but there are several things that can be done to minimize them. If a system is architected correctly, lazy loading is implemented correctly, and the NHibernate logs are checked judiciously for any potential runaway querying, the performance impact can be minimized. NHibernate also supports using native queries for problem areas where NHibernate is not achieving the desired result. In addition, NHibernate has several caching options to further tweak the performance.
Even when used correctly, NHibernate's performance of may never reach the perfection that customized queries and stored procedures can. A good team of programmers can design and create an incredibly fast custom data access layer for an application, but only if they have ample time and an ideal development environment. Unfortunately, ample time and ideal development environments are not always available. Because of this, custom data access layers for applications are often not optimized to the extent they could be. In fact, in many cases, especially with larger projects and larger teams, the data layer can become a mess with many conflicting and inconsistent practices that can hurt performance far more than NHibernate ever will. With NHibernate, your data access layer will at least be consistent and with minimal effort by the project team.
When it is all said and done, the point of NHibernate is not necessarily to make the performance of the code better, but to make the performance of the programmers better.
NHibernate is a great ORM solution for handling database persistence of business objects in an application. NHibernate is a large framework with many features and options that accommodate a large range of application designs. The example I went through in this article is far from a complete guide to everything NHibernate can do, but it is a good start and touches on many key NHibernate concepts. For continued reading, check out the documentation on the NHibernate web site. Also, in January 2007 the book NHibernate in Action will be released. Its Java counterpart, Hibernate in Action, has proved an invaluable resource on Hibernate functionality for Java applications.
About the Author
David Consdorf, who resides in Chicago, Illinois, has been developing Java and ASP.NET web applications as a software consultant for Crowe Chizek and Company LLC for the last three years. Crowe Chizek and Company LLC is a Microsoft Gold Certified Partner.
David graduated with a Bachelor's Degree in Computer Science from the University of Illinois Urbana-Champaign.