It seems like every single project we begin as developers, no matter how simple, requires some sort of storage. Sometimes this is a simple collection of values in an XML file or a key-value pair in a properties file.
However, more often, we need to have access to larger volumes of data, represented in multiple related database tables. In either case, we are generally forced to reinvent the wheel, to create new data retrieval and storage methods for each piece of data we want to access. Enter NHibernate.
In this chapter, we will discuss:
What NHibernate is and why we should use it
HBM mapping files
Plain Old CLR Objects (POCOs)
Data access classes
A simple web page databound to a collection of NHibernate objects
That's a great question, and I'm glad you asked! NHibernate is an open source persistence layer based on Object-Relational Mapping Techniques or simply a tool that creates a "virtual representation" of database objects within the code. According to the creators of NHibernate:
NHibernate is a port of Hibernate Core for Java to the .NET Framework. It handles persisting plain .NET objects to and from an underlying relational database. Given an XML description of your entities and relationships, NHibernate automatically generates SQL for loading and storing the objects.
In simple terms, NHibernate does all the database work, and we reap all the benefits! Instead of writing reams of SQL statements or creating stored procedures that "live" in a different place than our code, we can have all of our data access logic contained within our application.
With a few simple "tricks" that we'll discuss in Chapter 4, Data Cartography, not only will our queries be effective, but they will also be validated by the compiler. Therefore, if our underlying table structure changes, the compiler will alert us that we need to change our queries!
Unless you love to write CRUD (Create, Retrieve, Update, Delete) methods over and over for each of the pieces of data you need to access (and I don't know a single developer who does), you are probably looking for a better method. If you're like me, then you know how to lay down an elegant database design (and if you don't, take a peek at Chapter 2, Database Layout and Design). Once the database is ready, you just want to use it!
Wouldn't it be nice to create a few tables, and in just a few minutes, have a working set of forms that you can use for all of your basic CRUD operations, as well as a full set of queries to access the most common types of data? We'll discuss some of the ways to automatically generate your NHibernate data files in Chapter 11, It's a Generation Thing.
The home of the NHibernate project is at http://www.nhforge.org, while the code is housed at SourceForge (http://sourceforge.net/projects/nhibernate/).
If you download the latest GA (Generally Available, also known as final or stable) bin release (binaries only, no source code) of the NHibernate project, you will have everything you need to get started. As of this writing, the current release is NHibernate-2.1.2.GA-bin, and all of the examples have been developed using this version. This version is available at http://downloads.sourceforge.net/project/nhibernate/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-bin.zip.
There is a great community site for NHibernate on the Web called the NHibernate Forge. It is located at http://www.nhforge.org/, and it provides a wealth of resources for the new and veteran NHibernate user.
A basic NHibernate project is composed of three major parts. You will need a mapping file to tell NHibernate how the database is or should be (see the Mapping our types section in Chapter 4) constructed, some data access methods to tell NHibernate what data you want to retrieve or store into the database, and a POCO to allow you to interact with the data. While XML mapping files are commonly used in NHibernate projects, they are not the only way to map data to POCOs (more in Chapter 4).
Take a look at some sample files, but don't get too hung up on them. We'll go into more detail in the later chapters.
The following code snippet shows the Login.hbm.xml
mapping file for this simple table, with all the information required not only to map the data, but also to create the database from the metadata contained within the mapping file. If we do not want to be able to generate the database from the mapping file, then we can omit all of the sql-type
, unique
, and index
properties.
Some immediate information you might pick up from the file are the name of the class that NHibernate will use to map database rows (BasicWebApplication.Common.DataObjects.Login
), which is defined in the <class>
tag. This says that the BasicWebApplication.Common.DataObjects.Login
object is contained in the BasicWebApplication
assembly. It further defines that the Login
table is the database table we will be mapping to, using the <table>
element.
There is an <id>
tag that defines what the unique identifier (ID) is for the database record, as well as how that identifier is expected to be created. In our case, the <generator class="hilo">
tag specifies that we will be using the hi/lo Persistent Object ID (POID) generator for IDs.
The four string fields FirstName
, LastName
, UserName
, and Password
are then mapped to the four database columns of the same names, using the <property>
tag.
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="BasicWebApplication.Common.DataObjects"assembly="BasicWebApplication"> <class name="Login" table="Login"> <id name="Id" type="Int32" unsaved-value="null"> <column name="Id" /> <generator class="hilo" /> </id> <property name="FirstName" type="String" /> <property name="LastName" type="String" /> <property name="UserName" type="String" /> <property name="Password" type="String" /> </class> </hibernate-mapping>
The
Login.cs
class shown in the following code snippet is the POCO, the class that NHibernate will use to map database rows. Each row in the database returned will be instantiated (also known as "newed up") in a new instance of the Login
class. The collection of rows will be returned as a generic IList
of Login
objects or an IList<Login>
.
Notice how each property in the class Login
maps directly to a property element in the hbm.xml
file. We really have five public properties on this object, Id
, FirstName
, LastName
, UserName
, and Password
. Each of these properties was defined earlier in the hbm.xml
file and mapped to a database field.
When NHibernate retrieves records from the database, it will create a new instance (also known as "new up") of a Login
object for each record it retrieves and use the public "setter" (set function) for each property to fill out the object.
public partial class Login { public Login() { } public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual string UserName { get; set; } public virtual string Password { get; set; } }
The final class, LoginDataControl.cs
, provides CRUD methods for data retrieval, storage, and removal. The session variable is an NHibernate session (you can find out more about session management in Chapter 5, The Session Procession).
This class defines a few simple CRUD methods that are used quite often when manipulating database records. The GetById(int id)
function allows the user to pass in an integer and retrieve the record with that ID. The GetAll()
method returns all of the records in a given table. GetCountOfAll()
returns a count of the records in the table, while allowing controls that handle pagination and record navigation to function.
public class LoginDataControl { public LoginDataControl() { } ISession session; public Login GetById(int id) { Login retVal = session.Get<Login>(id); return retVal; } public IList<Login> GetAll() { ICriteria criteria = session.CreateCriteria<Login>(); IList<Login> retVal = criteria.List<Login>(); return retVal; } public int GetCountOfAll() { return GetAll().Count; } }
The sample Login.aspx
ASP.NET file shows one of the best reasons why we use NHibernate. By using an ObjectDataSource, we can map the NHibernate objects directly to the data-bound controls that will display or interact with them. All we have to do is create an ObjectDataSource to retrieve the data from our data access class (LoginDataControl.cs
), create a set of form fields to display the data (like the <asp:GridView>
"LoginGrid" below), and let ASP.NET handle all of the tedious work for us. By the way, this page will work exactly as shown—there is no page logic in the code behind or anywhere else.
All we have in this code is a GridView to present the information and an ObjectDataSource to interact with our DataAccess classes and provide data for the GridView. The GridView has BoundField definitions for all of the fields in our database table as well as Sorting and Paging functions. The ObjectDataSource has methods mapped for Select
, Select
Count
, Insert
, and Update
. When the GridView needs to perform one of these functions, it relies on the ObjectDataSource to handle these operations. Working in tandem, these two controls (as well as nearly any other data bound control) can provide a very quick and simple interface for your data!
<%@ Page Language="C#" AutoEventWireup="true"CodeBehind="Default.aspx.cs" Inherits="BasicWebApplication.Web._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <asp:GridView ID="LoginGrid" AutoGenerateColumns="false" DataSourceID="LoginSource" runat="server"> <Columns> <asp:HyperLinkField HeaderText="ID" DataTextField="Id" SortExpression="Id" DataNavigateUrlFields="Id"DataNavigateUrlFormatString="~/SampleForms/Login.aspx?LoginId={0}"Target="_parent" /> <asp:BoundField HeaderText="FirstName" DataField="FirstName" /> <asp:BoundField HeaderText="LastName" DataField="LastName" /> <asp:BoundField HeaderText="UserName" DataField="UserName" /> <asp:BoundField HeaderText="Password" DataField="Password" /> </Columns> </asp:GridView> <asp:ObjectDataSource ID="LoginSource" TypeName="BasicWebApplication.DataAccess.LoginDataControl"DataObjectTypeName="BasicWebApplication.Common.DataObjects.Login" SelectMethod="GetAll" SelectCountMethod="GetCountOfAll" runat="server"></asp:ObjectDataSource> </form> </body> </html>
In this chapter, we talked a little bit about what NHibernate is, and why we should use it. We also touched on what HBM mapping files are and what they are used for, as well as the Plain Old CLR Objects (POCOs) that NHibernate actually maps data into. Neither of these would be very helpful to us without some Data Access Object (DAO) classes to tell NHibernate to retrieve or save the data we are working with. Finally we looked at a simple web page that was databound to a collection of NHibernate objects, all without any codebehind or other additional code.
It may seem like creating all these files is a lot of work, and it might be simpler to just go back to handcoding the SQL! I would tend to agree with you, if I didn't know the shortcut to creating all of these files—code generation, or even better, using Fluent NHibernate! If you can't wait, then sneak a peek at Chapter 4, Data Cartography, for more about Fluent NHibernate.
Now that we have skimmed the surface on how NHibernate works and how to make it work for us, let's talk about database layout and design, which is the subject of our next chapter.