Last time we introduced setting up the configuration file. This time we will look at the basics of mapping.
Mapping Entities
How you map your entities depends upon your preferred
approach.
Attributes or mapping files
You can use either attributes on classes, or an xml based
mapping file to map between a class and a table. Attributes are less verbose
than xml files because you only need to say how to map to the Db, not what
object you are mapping. Some people also prefer them because they dislike
managing xml files. At the same time the issue with attributes files is that
they do not enforce a clean seperation of concerns. The domain should not know
how it is persisted.
Data First or Domain First
If you are ‘data-first’ or you have a legacy schema to
support then you may want to generate your entity classes from the domain
model. There are a number of templates and tools that could help you.
If you are domain first then you build your domain and then
generate the data schema from it. You can use the SchemaExport class to create
an initial cut of the schema by calling its Execute method. You can also use
Export to create an update schema. This model works well with ideas like
Test-Driven Development and Incremental Re-architecture as it allows you to
keep rebuilding your schema
NHibernate mapping – the basics
Let’s talk about xml based mapping, it’s the oldest and the
cleanest, and the other methods are pretty easy to figure out once you have
that one resolved.
We’ll use a simple model so we can focus on introducing
mapping, instead of delving into its depths.
If you are going to be working on mapping then you should
copy the schemas so that Visual Studio can provide you with intellisense:
Copy nhibernate-configuration.xsd, nhibernate-mapping.xsd,
and nhibernate-generic.xsd to the Visual Studio schemas directory. By default, the schemas directory is
C:\Program Files\Microsoft Visual Studio 9\Xml\Schemas.
We have a customer
public class Customer
{
public Company company { get; set; }
public DateTime? DateOfBirth { get;
set; }
public string FirstName {get;set;}
public int Id { get; set; }
public string Surname {get;set;}
public int Version { get; set; }
}
And a company
public class Company
{
private ISet<Customer> customers =
new HashedSet<Customer>();
public int Id { get; set; }
public string Name { get; set; }
public IList<Customer> customers
{
get { return customers; }
set
{
customers = value;
}
}
}
Note that we use ISet
for the collection type. Most ORM tools require us to do something similar to support lazy loading: use an interface instead of a concrete
type so that the framework can swap in a proxied collection which in turn asks the ORM
to load the data on demand.
Also note that we are mapping a nullable type. NHibernate
supports nullable types, particularly useful in the case of DateTime which
otherwise generates a date that SQL Server will not accept.
We can create a mapping file to map these tables to a Db.
The convention is to have one mapping file per entity, named after the entity
and with the extension *.hbm.xml. Remember to make sure that you make your
*.hbm.xml file an embedded resource. We usually associate the resource with the
class in the same assembly.
The mapping file
We need to tell NHibernate that it is an xml mapping file:
<?xml
version=”1.0″?>
and then we need to configure NHibernate itself
<hibernate-mapping
xmlns=”urn:nhibernate-mapping-2.2″
assembly=”NHibernateDemo” namespace=”NHibernateDemo.Domain”
default-lazy=”false”>
</hibernate-mapping>
Note that we need to check that we are using the create
version of the schema for the version of NHibernate we are building to. In this
case we are building to version 2.2 of the schema and working against the trunk
of NHibernate.
Note that we have turned off lazy loading by default.
NHibernate 2.0 turns on lazy-loading by default, which would means that your
classes need to have virtual methods and properties so that they can be
replaced by a proxy. For now we switch this off. We can still set it for a
relation or class explicitly.
The assembly and namespace attributes are a convenience to
save us re-typing them every time.
Mapping a class
Next we map the class
<class
name=”Customer” table=”Customer”>
</class>
We mainly need to map a class to a table, if we don’t have a
table yet this is where we name it. We can also define some options at this
level. We can set cascade and lazy-load options for the class and optimize how
update statements are generated. For now we can leave these to focus on how we
map, tweaking them later. It is worth pointing out that NHibernate is optimized
for using a version field to support optimistic locking.
In order to lazy load a class (as opposed to an association)
then we need to make the methods and properties of the class virtual (so that
NHibernate can generate a proxy to wrap our class, that asks for the concrete
class to be loaded when we need it).
Mapping the Entity’s Identity
Column
All entities must have a unique identifier, that is consistent
throughout their lifetime, which the Db represents as a primary key. We denote
an id mapping by using the <id/> element. We set the name of the id
property on the class. Again there is a range of mapping options, for example
setting the default value for new, unsaved entities, so that we, and
NHibernate, can easily recognize unsaved entities.
We need to use the <generator/> element within the
<id/> element to indicate how we create new id numbers when we persist.
For databases which support identity columns (DB2, MySQL, Sybase, MS SQL), you
may use identity key generation. For databases that support sequences (DB2,
Oracle, PostgreSQL, Interbase, McKoi, SAP DB) you may use sequence style key
generation.
Mapping the entity version
NHibernate recommends using a <version/> element for
optimistic concurrency support (as opposed to field-by-field comparison or a
timestamp).
<class name=”Customer”
table=”Customer”>
<id name=”Id”
type=”Int32″ unsaved-value=”0″>
<generator
class=”identity”/>
</id>
<version name=”Version”>
</version>
</class>
Mapping a property
We map a property to a field by using the <property>
element. We can just supply the property name, though once again there are
options, for example to exclude fields that we never update (i.e. read-only) or
to assign a calculated value.
<class name=”Customer”
table=”Customer”>
<id name=”Id”
type=”Int32″ unsaved-value=”0″>
<generator
class=”identity”/>
</id>
<version name=”Version”/>
<property name=”DateOfBirth”/>
<property
name=”FirstName”/>
<property name=”Surname”/>
</class>
Next time we’ll look at mapping relationships using NHibernate.