Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

440 lines (366 sloc) 18.762 kB
<chapter id="quickstart">
<title>Quickstart with IIS and Microsoft SQL Server</title>
<sect1 id="quickstart-intro">
<title>Getting started with NHibernate</title>
<para>
This tutorial explains a setup of NHibernate 1.0.2 within a Microsoft
environment. The tools used in this tutorial are:
</para>
<orderedlist>
<listitem>
Microsoft Internet Information Services (IIS) - web server supporting
ASP.NET.
</listitem>
<listitem>
Microsoft SQL Server 2000 - the database server. This tutorial uses
the desktop edition (MSDE), a free download from Microsoft. Support
for other databases is only a matter of changing the NHibernate SQL
dialect and driver configuration.
</listitem>
<listitem>
Microsoft Visual Studio .NET 2003 - the development environment.
</listitem>
</orderedlist>
<para>
First, we have to create a new Web project. We use the name <literal>QuickStart</literal>,
the project web virtual directory will <literal>http://localhost/QuickStart</literal>.
In the project, add a reference to <literal>NHibernate.dll</literal>. Visual Studio
will automatically copy the library and its dependencies to the project output directory.
If you are using a database other than SQL Server, add a reference to the driver assembly
to your project.
</para>
<para>
We now set up the database connection information for NHibernate. To do this, open
the file <literal>Web.config</literal> automatically generated for your project and add
configuration elements according to the listing below:
</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Add this element -->
<configSections>
<section
name="hibernate-configuration"
type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"
/>
</configSections>
<!-- Add this element -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.connection_string">Server=(local);initial catalog=quickstart;Integrated Security=SSPI</property>
<mapping assembly="QuickStart" />
</session-factory>
</hibernate-configuration>
<!-- Leave the system.web section unchanged -->
<system.web>
...
</system.web>
</configuration>]]></programlisting>
<para>
The <literal>&lt;configSections&gt;</literal> element contains definitions of
sections that follow and handlers to use to process their content. We declare
the handler for the configuration section here. The <literal>
&lt;hibernate-configuration&gt;</literal> section contains the configuration
itself, telling NHibernate that we will use a Microsoft SQL Server 2000
database and connect to it through the specified connection string.
The dialect is a required setting, databases differ in their interpretation
of the SQL "standard". NHibernate will take care of the differences and comes
bundled with dialects for several major commercial and open source databases.
</para>
<para>
An <literal>ISessionFactory</literal> is NHibernate's concept of a single
datastore, multiple databases can be used by creating multiple XML
configuration files and creating multiple <literal>Configuration</literal>
and <literal>ISessionFactory</literal> objects in your application.
</para>
<para>
The last element of the <literal>&lt;hibernate-configuration&gt;</literal>
section declares <literal>QuickStart</literal> as the name of an assembly
containing class declarations and mapping files. The mapping files
contain the metadata for the mapping of the POCO class to a database table
(or multiple tables). We'll come back to mapping files soon. Let's write the
POCO class first and then declare the mapping metadata for it.
</para>
</sect1>
<sect1 id="quickstart-persistentclass">
<title>First persistent class</title>
<para>
NHibernate works best with the Plain Old CLR Objects (POCOs, sometimes
called Plain Ordinary CLR Objects) programming model for persistent classes.
A POCO has its data accessible through the standard .NET property mechanisms,
shielding the internal representation from the publicly visible interface:
</para>
<programlisting><![CDATA[using System;
namespace QuickStart
{
public class Cat
{
private string id;
private string name;
private char sex;
private float weight;
public Cat()
{
}
public string Id
{
get { return id; }
set { id = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public char Sex
{
get { return sex; }
set { sex = value; }
}
public float Weight
{
get { return weight; }
set { weight = value; }
}
}
}]]></programlisting>
<para>
NHibernate is not restricted in its usage of property types, all .NET
types and primitives (like <literal>string</literal>, <literal>char</literal>
and <literal>DateTime</literal>) can be mapped, including classes from the
<literal>System.Collections</literal> namespace. You can map them as values,
collections of values, or associations to other entities. The <literal>Id</literal>
is a special property that represents the database identifier (primary key) of
that class, it is highly recommended for entities like a <literal>Cat</literal>.
NHibernate can use identifiers only internally, without having to declare them
on the class, but we would lose some of the flexibility in our application
architecture.
</para>
<para>
No special interface has to be implemented for persistent classes nor do we have
to subclass from a special root persistent class. NHibernate also doesn't use any
build time processing, such as IL manipulation, it relies solely on
.NET reflection and runtime class enhancement (through Castle.DynamicProxy library).
So, without any dependency in the POCO class on NHibernate, we can map it to
a database table.
</para>
</sect1>
<sect1 id="quickstart-mapping">
<title>Mapping the cat</title>
<para>
The <literal>Cat.hbm.xml</literal> mapping file contains the metadata
required for the object/relational mapping. The metadata includes declaration
of persistent classes and the mapping of properties (to columns and
foreign key relationships to other entities) to database tables.
</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="QuickStart" assembly="QuickStart">
<class name="Cat" table="Cat">
<!-- A 32 hex character is our surrogate key. It's automatically
generated by NHibernate with the UUID pattern. -->
<id name="Id">
<column name="CatId" sql-type="char(32)" not-null="true"/>
<generator class="uuid.hex" />
</id>
<!-- A cat has to have a name, but it shouldn' be too long. -->
<property name="Name">
<column name="Name" length="16" not-null="true" />
</property>
<property name="Sex" />
<property name="Weight" />
</class>
</hibernate-mapping>]]></programlisting>
<para>
Every persistent class should have an identifer attribute (actually, only
classes representing entities, not dependent value objects, which
are mapped as components of an entity). This property is used to distinguish
persistent objects: Two cats are equal if
<literal>catA.Id.Equals(catB.Id)</literal> is true, this concept is
called <emphasis>database identity</emphasis>. NHibernate comes bundled with
various identifer generators for different scenarios (including native generators
for database sequences, hi/lo identifier tables, and application assigned
identifiers). We use the UUID generator (only recommended for testing, as integer
surrogate keys generated by the database should be prefered) and also specify the
column <literal>CatId</literal> of the table <literal>Cat</literal> for the
NHibernate generated identifier value (as a primary key of the table).
</para>
<para>
All other properties of <literal>Cat</literal> are mapped to the same table. In
the case of the <literal>Name</literal> property, we mapped it with an explicit
database column declaration. This is especially useful when the database
schema is automatically generated (as SQL DDL statements) from the mapping
declaration with NHibernate's <emphasis>SchemaExport</emphasis> tool. All other
properties are mapped using NHibernate's default settings, which is what you
need most of the time. The table <literal>Cat</literal> in the database looks
like this:
</para>
<programlisting><![CDATA[ Column | Type | Modifiers
--------+--------------+----------------------
CatId | char(32) | not null, primary key
Name | nvarchar(16) | not null
Sex | nchar(1) |
Weight | real |]]></programlisting>
<para>
You should now create the database and this table manually, and later read
<xref linkend="toolsetguide"/> if you want to automate this step with the
SchemaExport tool. This tool can create a full SQL DDL, including table
definition, custom column type constraints, unique constraints and indexes.
If you are using SQL Server, you should also make sure the <literal>ASPNET</literal>
user has permissions to use the database.
</para>
</sect1>
<sect1 id="quickstart-playingwithcats" revision="1">
<title>Playing with cats</title>
<para>
We're now ready to start NHibernate's <literal>ISession</literal>. It is the
<emphasis>persistence manager</emphasis> interface, we use it
to store and retrieve <literal>Cat</literal>s to and from the database.
But first, we've to get an <literal>ISession</literal> (NHibernate's unit-of-work)
from the <literal>ISessionFactory</literal>:
</para>
<programlisting><![CDATA[ISessionFactory sessionFactory =
new Configuration().Configure().BuildSessionFactory();]]></programlisting>
<para>
An <literal>ISessionFactory</literal> is responsible for one database and
may only use one XML configuration file (<literal>Web.config</literal> or
<literal>hibernate.cfg.xml</literal>).
You can set other properties (and even change the mapping metadata) by
accessing the <literal>Configuration</literal> <emphasis>before</emphasis>
you build the <literal>ISessionFactory</literal> (it is immutable). Where
do we create the <literal>ISessionFactory</literal> and how can we access
it in our application?
</para>
<para>
An <literal>ISessionFactory</literal> is usually only built once,
e.g. at startup inside <literal>Application_Start</literal> event handler.
This also means you should not keep it in an instance variable in your
ASP.NET pages, but in some other location. Furthermore, we need some kind of
<emphasis>Singleton</emphasis>, so we can access the
<literal>ISessionFactory</literal> easily in application code. The approach
shown next solves both problems: configuration and easy access to a
<literal>ISessionFactory</literal>.
</para>
<para>
We implement a <literal>NHibernateHelper</literal> helper class:
</para>
<programlisting><![CDATA[using System;
using System.Web;
using NHibernate;
using NHibernate.Cfg;
namespace QuickStart
{
public sealed class NHibernateHelper
{
private const string CurrentSessionKey = "nhibernate.current_session";
private static readonly ISessionFactory sessionFactory;
static NHibernateHelper()
{
sessionFactory = new Configuration().Configure().BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
HttpContext context = HttpContext.Current;
ISession currentSession = context.Items[CurrentSessionKey] as ISession;
if (currentSession == null)
{
currentSession = sessionFactory.OpenSession();
context.Items[CurrentSessionKey] = currentSession;
}
return currentSession;
}
public static void CloseSession()
{
HttpContext context = HttpContext.Current;
ISession currentSession = context.Items[CurrentSessionKey] as ISession;
if (currentSession == null)
{
// No current session
return;
}
currentSession.Close();
context.Items.Remove(CurrentSessionKey);
}
public static void CloseSessionFactory()
{
if (sessionFactory != null)
{
sessionFactory.Close();
}
}
}
}]]></programlisting>
<para>
This class does not only take care of the <literal>ISessionFactory</literal>
with its static attribute, but also has code to remember the <literal>ISession</literal>
for the current HTTP request.
</para>
<para>
An <literal>ISessionFactory</literal> is threadsafe, many threads can access
it concurrently and request <literal>ISession</literal>s. An <literal>ISession</literal>
is a non-threadsafe object that represents a single unit-of-work with the database.
<literal>ISession</literal>s are opened by an <literal>ISessionFactory</literal> and
are closed when all work is completed:
</para>
<programlisting><![CDATA[ISession session = NHibernateHelper.GetCurrentSession();
ITransaction tx = session.BeginTransaction();
Cat princess = new Cat();
princess.Name = "Princess";
princess.Sex = 'F';
princess.Weight = 7.4f;
session.Save(princess);
tx.Commit();
NHibernateHelper.CloseSession();]]></programlisting>
<para>
In an <literal>ISession</literal>, every database operation occurs inside a
transaction that isolates the database operations (even read-only operations).
We use NHibernate's <literal>ITransaction</literal> API to abstract from the underlying
transaction strategy (in our case, ADO.NET transactions). Please note that the example
above does not handle any exceptions.
</para>
<para>
Also note that you may call <literal>NHibernateHelper.GetCurrentSession();</literal>
as many times as you like, you will always get the current <literal>ISession</literal>
of this HTTP request. You have to make sure the <literal>ISession</literal> is closed
after your unit-of-work completes, either in <literal>Application_EndRequest</literal>
event handler in your application class or in a <literal>HttpModule</literal> before
the HTTP response is sent. The nice side effect of the latter is easy lazy
initialization: the <literal>ISession</literal> is still open when the view is
rendered, so NHibernate can load unitialized objects while you navigate the graph.
</para>
<para>
NHibernate has various methods that can be used to retrieve objects from the
database. The most flexible way is using the Hibernate Query Language (HQL),
which is an easy to learn and powerful object-oriented extension to SQL:
</para>
<programlisting><![CDATA[ITransaction tx = session.BeginTransaction();
IQuery query = session.CreateQuery("select c from Cat as c where c.Sex = :sex");
query.SetCharacter("sex", 'F');
foreach (Cat cat in query.Enumerable())
{
Console.Out.WriteLine("Female Cat: " + cat.Name);
}
tx.Commit();]]></programlisting>
<para>
NHibernate also offers an object-oriented <emphasis>query by criteria</emphasis> API
that can be used to formulate type-safe queries. NHibernate of course uses
<literal>IDbCommand</literal>s and parameter binding for all SQL communication
with the database. You may also use NHibernate's direct SQL query feature or
get a plain ADO.NET connection from an <literal>ISession</literal> in rare cases.
</para>
</sect1>
<sect1 id="quickstart-summary">
<title>Finally</title>
<para>
We only scratched the surface of NHibernate in this small tutorial. Please note that
we don't include any ASP.NET specific code in our examples. You have to create an
ASP.NET page yourself and insert the NHibernate code as you see fit.
</para>
<para>
Keep in mind that NHibernate, as a data access layer, is tightly integrated into
your application. Usually, all other layers depend on the persistence mechanism.
Make sure you understand the implications of this design.
</para>
</sect1>
</chapter>
Jump to Line
Something went wrong with that request. Please try again.