left-icon

NHibernate Succinctly®
by Ricardo Peres

Previous
Chapter

of
A
A
A

CHAPTER 4

Mappings

Mappings


Concepts

We essentially define mappings for:

  • Entities.
  • Entity identifiers.
  • Entity properties.
  • References to other entities.
  • Collections.
  • Inheritance.

Identifiers, properties, references, and collections are all members of the entity class where:

  • Identifiers can either be of simple .NET types, such as those one would find as table columns (such as Int32, String or Boolean), but also common value types such as DateTime, Guid or TimeSpan or, for representing composite primary keys, they can also be of some custom class type.
  • Scalar properties (or just properties) are also of primitive or common value types; other cases are Byte[] for generic Binary Large Objects (BLOBs) and XDocument/XmlDocument for XML data types.
  • Complex properties, known as value objects in Domain-Driven Design (DDD) and as components in NHibernate, are classes with some properties that are logically grouped together. Think of a postal address, for example, which may be comprised of a street address, zip code, city, country, etc. Of course, in the database, these are also stored as scalar columns.
  • References to other entities (one-to-one, many-to-one) are declared as the type of the entity in the other endpoint of the relation; in the database, these are foreign key columns.
  • Collections of entities (one-to-many, many-to-many) are declared as collections of the other endpoint’s entity type. Of course, in the database these are also stored as foreign key columns.

Let’s look at each of these concepts in more detail.

Entities

An entity is at the core of a NHibernate mapping. It is a .NET class that will be saved into one (or more, as we will see) table. Its most important configurable options are:

  • Name: The name of the class; a mandatory setting.
  • Table: The table to which the class is mapped, also a mandatory setting unless we are defining a hierarchy of classes (see Entity Byte[]).
  • Laziness: If the entity’s class allows creating a proxy to it, thus allowing lazy loading. The default is true.
  • Mutable: If any changes to the class’ members should be persisted to the database. The default is true.
  • Optimistic lock: The strategy to be followed for optimistic concurrency control (see Optimistic Locking). The default is none.
  • Where restriction: An optional SQL restriction (see Restrictions).
  • Batch size: The number of additional instances of the same type that will be loaded automatically by NHibernate. You will see a more detailed explanation of this setting in section Batch Loading. It is not a required setting and the default value is 0.

Properties

A property either maps to a column in a table or to a SQL formula, in which case it will not be persisted but instead calculated when the containing entity is loaded from the database. A property has the following attributes:

  • Name: The name of the property in the entity class; required.
  • Column: The name of the column to which it maps; required unless a formula is used.
  • Length: For string or array properties, the maximum length of the column in the table. It is only used when generating the table or the column; not required an the default value is 255.
  • Not Null: Indicates if the properties’ underlying column takes NULL values; must match the property type (for value types, if they might be nullable on the database, they must be declared with ?); not required, the default is false.
  • Formula: If set, it should be a SQL statement that may reference other columns of the entity’s table; if set, the column property has no meaning.
  • Unique: Indicates if the column should be unique; only used when the table or column is generated. Default is false.
  • Optimistic Lock: If set to false, it won’t consider this property for determining if the entity’s version should change (more on versioning in the Optimistic Locking chapter). It is not required and the default value is true.
  • Update: Indicates if the column is considered for updating when the entity is updated; the default is true.
  • Insert: If the column should be inserted or not; the default is true.
  • Generated: If set, indicates if the column’s value is generated upon insertion (insert) or always, generally by means of a trigger. The default is never, and if something else is used, an extra SELECT is needed after the insertion or update of the entity to know its value.
  • Lazy: Should the column be loaded when the containing entity is loaded, we should set this to true if the column contains large contents (CLOBs or BLOBs) and its value may not always be required. The default is false.
  • Type: If the property is not one of the primitive types, it should contain the full name of a concrete .NET class, which is responsible for creating the property’s actual value from the column coming from the database and for translating it back into the database. There are several type implementations included with NHibernate.
  • Mutable: Do changes to this property be reflected to the database? The default is true.

For scalar properties, either at entity level or as members of a component, NHibernate supports the following .NET types:

NHibernate Recognized Types

.NET Type

OOTB

Purpose

Boolean

Yes

A boolean or single bit (0/1 value)

Byte/SByte

Yes

8-bit signed/unsigned integer number, usually for representing a single ANSI character

Byte[]

Yes

Stored in a BLOB

Char

Yes

Single ANSI or UNICODE character

CultureInfo

Yes

Stored as a string containing the culture name

DateTime

Yes

Date and time

DateTimeOffset

Yes

Date and time with offset relative to UTC

Decimal

Yes

128-bit signed integers, scaled by a power of 10

Double

Yes

Double precision (64-bit) signed floating point values

Enum (enumerated type)

Yes/No

Stored as an integer (default), as a character or as a string

Guid

Yes

GUIDs or generic 128-bit numbers

Int16/UInt16

Yes

16-bit signed/unsigned integer numbers

Int32/UInt32

Yes

32-bit signed/unsigned integer numbers

Int64/UInt64

Yes

64-bit signed/unsigned integer numbers

Object (serializable)

No

Stored in a BLOB containing the object’s contents after serialization

Single

Yes

Single precision (32-bit) signed floating point values

String

Yes

ANSI or UNICODE variable or fixed-length characters

TimeSpan

Yes

Time

Type

Yes

Stored as a string containing the assembly qualified type name

Uri

Yes

Stored as a string

XDocument/XmlDocument

Yes

XML

The second column indicates if the property’s type is supported out of the box by NHibernate, that is, without the need for additional configuration. If we need to map a nonstandard primitive type or one of those other types that NHibernate recognizes out of the box (Enum, DateTime, DateTimeOffset, Guid, TimeSpan, Uri, Type, CultureInfo, Byte[]) or if we need to change the way a given property should be handled, we need to use a custom user type. For common cases, no type needs to be specified. For other scenarios, the available types are:

NHibernate Types

NHibernate Type

.NET Property Type

Description

AnsiCharType

Char

Stores a Char as an ANSI (8 bits per character) column instead of UNICODE (16 bits). Some national characters may be lost.

AnsiStringType

String

Stores a String as an ANSI (8 bits per character) column instead of UNICODE (16 bits). Some national characters may be lost.

BinaryBlobType

Byte[]

Will store a Byte[] in a database-specific BLOB. If the database requires defining a maximum length, as in SQL Server, use BinaryBlobType.

CharBooleanType

Boolean

Converts a Boolean value to either True or False

DateType

DateTime

Stores only the date part of a DateTime

EnumCharType<T>

Enum (enumerated type)

Stores an enumerated value as a single character, obtained from its numeric value

EnumStringType<T>

Enum (enumerated type)

Stores an enumerated value as its string representation instead of its numeric value

LocalDateTimeType

DateTime

Stores a DateTime as local

SerializableType

Object (serializable)

Serializes an Object into a database-specific BLOB type.

StringClobType

String

Will store a String in a database-specific CLOB type instead of a standard VARCHAR

TicksType

DateTime

Stores a DateTime as a number of ticks

TrueFalseType

Boolean

Converts a Boolean value to either T or F

UtcDateTimeType

DateTime

Stores a DateTime as UTC

YesNoType

Boolean

Converts a Boolean value to either Y or N

Complex properties differ from references to other entities because all of their inner properties are stored in the same class as the declaring entity, and complex properties don’t have anything like an identifier, just a collection of scalar properties. They are useful for logically grouping together some properties that conceptually are related (think of an address, for example). A component may be used in several classes and its only requirements are that it is not abstract and that it has a public, parameterless constructor.

A property does not have to be public but, at the least, it should be protected, not private. Its visibility will affect the ability of the querying APIs to use it; LINQ, for example, will only work with public properties. Different access levels for setters and getters are fine, too, as long as you keep them protected at most.

Finally, although you can use properties with an explicit backing field, there is really no need to do so. In fact, some functionality will not work with backing fields and, as a rule of thumb, you should stick to auto properties.

Custom Types

Some scenarios where we need to specify a type include, for example, when we need to store a String in a BLOB column such as VARBINARY(MAX) in SQL Server or when we need to store just the date part of a DateTime or even when we need to store a Boolean as its string representation (True/False). Like I said, in most cases, you do not need to worry about this.

You can create your own user type, for example, if you want to expose some columns as a different type—like converting a BLOB into an Image. See the following example:

[Serializable]

public sealed class ImageUserType : IUserTypeIParameterizedType

{

  private Byte[] data = null;

 

  public ImageUserType() : this(ImageFormat.Png)

  {

  }

 

  public ImageUserType(ImageFormat imageFormat)

  {

    this.ImageFormat = imageFormat;

  }

 

  public ImageFormat ImageFormat { get; private set; }

 

  public override Int32 GetHashCode()

  {

    return ((this as IUserType).GetHashCode(this.data));

  }

 

  public override Boolean Equals(Object obj)

  {

    ImageUserType other = obj as ImageUserType;

    if (other == null)

    {

      return (false);

    }

 

    if (Object.ReferenceEquals(this, other) == true)

    {

      return (true);

    }

 

    return (this.data.SequenceEqual(other.data));

  }

  Boolean IUserType.IsMutable

  {

    get

    {

      return (true);                                 

    }

  }

 

  Object IUserType.Assemble(Object cached, Object owner)

  {

    return (cached);

  }

 

  Object IUserType.DeepCopy(Object value)

  {

    if (value is ICloneable)

    {

      return ((value as ICloneable).Clone());

    }

    else

    {

      return (value);

    }

  }

  Object IUserType.Disassemble(Object value)

  {

    return ((this as IUserType).DeepCopy(value));

  }

 

  Boolean IUserType.Equals(Object x, Object y)

  {

    return (Object.Equals(x, y));

  }

 

  Int32 IUserType.GetHashCode(Object x)

  {

    return ((x != null) ? x.GetHashCode() : 0);

  }

 

  Object IUserType.NullSafeGet(IDataReader rs, String[] names, Object owner)

  {

    this.data = NHibernateUtil.Binary.NullSafeGet(rs, names) as Byte[];

    if (data == null)

    {

      return (null);

    }

 

    using (Stream stream = new MemoryStream(this.data ?? new Byte[0]))

    {

      return (Image.FromStream(stream));

    }

  }

 

  void IUserType.NullSafeSet(IDbCommand cmd, Object value, Int32 index)

  {

    if (value != null)

    {

      Image data = value as Image;

      using (MemoryStream stream = new MemoryStream())

      {

        data.Save(stream, this.ImageFormat);

        value = stream.ToArray();

      }

    }

 

    NHibernateUtil.Binary.NullSafeSet(cmd, value, index);  

  }

 

  Object IUserType.Replace(Object original, Object target, Object owner)

  {

    return (original);

  }

 

  Type IUserType.ReturnedType

  {

    get

    {

      return (typeof(Image));

    }

  }

  SqlType[] IUserType.SqlTypes

  {

    get

    {

      return (new SqlType[] { NHibernateUtil.BinaryBlob.SqlType });

    }

  }

 

  void IParameterizedType.SetParameterValues(IDictionary<StringString> parameters)

  {

    if ((parameters != null) && (parameters.ContainsKey("ImageFormat") == true))

    {

      this.ImageFormat = typeof(ImageFormat).GetProperty(parameters["ImageFormat"], 

BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty).GetValue(nullnullas ImageFormat;

    }

  }

}

The most important aspects are:

  • How the data is retrieved from and saved back to the data source (NullSafeGet and NullSafeSet methods)
  • The value comparison, for letting NHibernate know if the property has changed (Equals and GetHashCode), for the purpose of change tracking
  • The database data type (SqlTypes)

Identifiers

An identifier is either a scalar property, for the most general case of single column primary keys, or a custom class that contains properties for all the columns that make up the composite key. If it is a scalar property, not all types are allowed. You should only use primitive types (Char, Byte/SByte, Int16/UInt16, Int32/UInt32, Int64/UInt64, Decimal, String) and some specific value types (Guid, DateTime, DateTimeOffset, TimeSpan). BLOB, CLOB, and XML columns cannot be used as primary keys.

An identifier property may also be non-public but the most restricted access level you should use is protected or protected internal. Protected setters and public getters are fine, too, and are actually a good idea—unless you want to use manually assigned identifiers.

A very important concept around identifiers is the generation strategy. Basically, this is how the primary key is generated when a record is to be inserted into the database. These are the most important high-level strategies:

NHibernate Identity Generators

Strategy Type

Generator (by code/XML)

Identifier Property Type

Description

Database-generated

Identity/identity

Sequence/sequence

Int16/UInt16, Int32/UInt32, Int64/UInt64

Identity columns are supported in SQL Server, MySQL, and Sybase, among others. Sequences are supported on Oracle and PostgreSQL, and can provide values for multiple tables. Both are safe for multiple simultaneous accesses but do not allow batching (more on this later).

GUID-based

Guid/guid

GuidComb/guid.comb

Guid

GUIDs are generated by NHibernate and are assumed to be unique. It is safe for multiple simultaneous accesses and it is possible to do batch insertions.

GuidComb is always sequencial.

Supported by a backing table

HighLow/hilo

Int16/UInt16, Int32/UInt32, Int64/UInt64

A table exists where the next high value is stored. When a session needs to insert records, it increments and updates this value and combines it with the next of a range of sequential low values until this range is exhausted and another high value needs to be retrieved. It is safe for both multiple simultaneous accesses and batching.

Manually assigned

Assigned/assigned

Any

You are responsible for assigning unique keys to your entities; use with care when you have multiple sessions inserting records.

There are other identifier generators but is advised that you stick with these because they are the ones supported by all mapping APIs. A discussion of these strategies is required.

Database-generated keys may be appealing because they are naturally used with common database programming and may appear as the natural choice. However, they do have some drawbacks:

  • Because the key is generated inside the database after a record is inserted, an additional immediate SELECT is required to obtain the generated key. This renders batching impossible because we cannot issue multiple INSERTs together.
  • Because both identity columns and sequences are designed for speed, they do not take into consideration database transactions. This means that, even if you rollback, the generated key, although not used, will be lost.
  • They do not comply with the concept of Unit of Work, in the sense that it will try to insert the new record as soon as the entity is marked for saving and not just when the Unit of Work is committed.
  • Most important: They are not database-independent. If you wish to use identity columns or sequences, you can only do it in engines that support this functionality, without changing the mapping configuration.

Tip: When using the Sequence generator, you have to supply the name for the actual sequence to be used as a parameter to the generator (see below).

GUIDs have some interesting aspects:

  • Because they are guaranteed to be unique, they are the obvious choice when it comes to designing a database that will be populated with records coming from third-party databases; there will never be clashes.
  • They are fast to compute.
  • They are database-independent.

They have one serious disadvantage, though: If we use clustered primary keys (the default in SQL Server), because generated GUIDs are not sequential they will make the engine continually reorganize the index, adding new keys either before or after the existing ones. To deal with this problem, NHibernate has implemented an alternative version, GuidComb, which is based on an algorithm designed by Jimmy Nilsson and available at http://www.informit.com/articles/article.aspx?p=25862. It basically consists of making a GUID sequential: two GUIDs generated in sequence are also numerically sequential, while remaining unique. If you really need a GUID as your primary key, do choose GuidComb. However, do keep in mind that GUIDs do take much more space—16 bytes as opposed to the four bytes of an integer.

The HighLow generation strategy is recommended for most scenarios. It is database-independent, allows batching, copes well with the Unit of Work concept, and, although it does require a SELECT and an UPDATE when the first record is to be inserted and additional ones when the low values are exhausted, the low values are immediately available. Plus, a range can be quite large, allowing for many records to be inserted without performing additional queries.

As for manually assigned identifiers, there are certainly scenarios where they are useful. But they have a big problem. Because NHibernate uses the identifier value (or lack thereof) to tell if a record is to be updated or inserted, it needs to issue a SELECT before persisting an entity in order to know if the record already exists. This defeats batching and may present problems if multiple sessions are to be used simultaneously.

Apart from the generation strategy, an identifier also supports the following properties:

  • Name: The name of the .NET entity property; required.
  • Column(s): The name of the column(s) that contain(s) the primary key; required.
  • Length: For string columns, will contain their size, only for the purpose of generating the table or column. If not set, it defaults to 255.
  • Unsaved Value: A textual representation of the value that the identifier property has before the identifier is generated; used for distinguishing if the entity is new or updated. If not set, it defaults to the identifier property’s underlying default value (0 for numbers, null for classes, etc).

It is possible to pass parameters to identifier generators for those that require it. One example would be specifying the sequence name for the Sequence generator. Examples are included in the mapping sections.

References

Like table relations, an entity may also be associated with another entity. It is said that the entity’s class has a reference for another entity’s class. There are two types of relations:

  • One-to-one: The primary key is shared between the two tables. For each record on the main table, there may be, at most, one record on the secondary table that refers to it. Useful when you have mandatory data and optional data that is associated with it.
  • Many-to-one: A table’s record may be associated with one record from another table. This other record may be referenced by multiple records on the main table. Think of it as a record’s parent.

A reference has the type of the class on the other endpoint. There can be even multiple references to the same class, of course, with different names. The properties of the reference are:

  • Name: Mandatory.
  • Required: If the record on the main class must reference an existing record on the second class. The default is false.
  • The relation type: Either one-to-one or many-to-one.
  • Laziness: The default is proxy, meaning that the referenced entity will not be loaded at the same time as its referencing entity but, rather, a proxy will be created for it (which will cause it to load when accessed). Other possibilities are false or no-proxy.
  • Not found: The behavior when the referenced record is not found. The default is exception and the other possibility is ignore.

Collections

An entity (parent) can be associated with multiple entities (children) of some type at the same time. These collections can be characterized in a number of ways:

  • Endpoint multiplicity: One-to-many (a parent has multiple children, a child only has one parent), many-to-many (a parent can have multiple children and each child can have multiple parents), and values (the children are not entities but values).
  • Relation between the parent and the children endpoints: Unidirectional (the parent knows the children but these do not know the parent), and bidirectional (both sides know each other).
  • Conceptually: Bag (allows duplicates, order does not matter), and set (does not allow duplicates, elements are either sorted or unsorted).
  • What they contain: Values (scalar values), components (complex values without an identity of their own), and entities (complex values with their own identity).
  • How they are accessed: Indexed or keyed (something is used for identifying each of the items in the collection) and non-indexed.
  • Where is the foreign key stored: Inverse (the foreign key is located at the child endpoint) or non-inverse.

NHibernate has the following collection types:

NHibernate Collection Types

Collection

Relations

Items to Store

Index Type

.NET Types

Set (non-indexed, bidirectional, inverse)

One-to-many, many-to-many

Entities, elements, components

N/A

IEnumerable<T>, ICollection<T>, Iesi.Collections.Generic.ISet<T>

Bag (non-indexed, bidirectional, inverse or non-inverse)

One-to-many, many-to-many, values

Entities, values, components

N/A

IEnumerable<T>, ICollection<T>, IList<T>

List (indexed, bidirectional, inverse or non-inverse)

One-to-many, many-to-many, values

Entities, values, components

Number

IEnumerable<T>, ICollection<T>, IList<T>

Map (indexed, unidirectional, inverse or non-inverse)

Many-to-many, values

Entities, values, components

Entity, scalar value

IDictionary<TKey, TValue>

Id Bag (non-indexed, unidirectional, inverse or non-inverse)

One-to-many, Many-to-many, values

Entities, values, components

Number

IEnumerable<T>, ICollection<T>, IList<T>

Array (indexed, unidirectional, inverse or non-inverse)

One-to-many, many-to-many, values

Entities, values, components

Number

IEnumerable<T>, ICollection<T>, T []

Primitive Array (indexed, unidirectional, non-inverse)

One-to-many, many-to-many, values

Values of primitive types

Number

IEnumerable<T>, ICollection<T>, T []


Tip: You cannot use the .NET BCL System.Collections.Generic.ISet<T>; as of now, NHibernate requires the use of Iesi.Collections.

Collections have the following attributes:

  • Name: This is mandatory.
  • Type (bag, set, list, map, id bag, array, primitive array): This is also mandatory.
  • Key: The name of the column or entity (in the case of maps) that contains the key of the relation.
  • Order: The column by which the collection’s elements will be loaded. Optional.
  • Whether the collection is inverse or not (set, list, id bag, array, primitive array): Default is false.
  • The entity or element type of its values: Mandatory.
  • Restriction: An optional restriction SQL (see Restrictions).
  • Laziness: The desired laziness of the collection (see Lazy Loading). The default is lazy.
  • Endpoint Multiplicity: One-to-many or many-to-many in the case of collections of entities. Mandatory.

Some remarks:

  • NHibernate fully supports generic collections and you really should use them.
  • All except array and primitive array support lazy loading (see Lazy Loading). Arrays and primitive arrays cannot change their size; that is, it is not possible to add or remove items.
  • The .NET type you use to declare the collection should always be an interface. The actual type will determine what you want to be able to do with the collection. For example, IEnumerable<T> does not allow modifications, which makes it a good choice for scenarios in which you don’t want users to change the content of the collection.
  • All collection types other than primitive array support entities, primitive types, and components as their items.
  • Id bags, lists, arrays, and primitive arrays use an additional table column, not mapped to a class property, to store the primary key (in the case of id bags) or the ordering. If you use NHibernate to create the data model, NHibernate will create it for you.
  • Bidirectional relations between entities are always inverse.
  • Maps, collections of components (complex properties), elements (primitive types), and primitive arrays are never inverse.
  • Maps and many-to-many relations always require an additional mapping table.
  • By far, the most often used collections are sets, lists, and maps.
  • When compared to bags, sets have the advantage in that they do not allow duplicates which, most of the time, is what we want.
  • Because bags do not know the key of their elements, whenever we add or remove from a bag, we need to remove and re-insert all elements, which leads to terrible performance. Id bags solve this problem by allowing an unmapped key column.
  • Lists are better than arrays as indexed collections because arrays cannot change their size.
  • Maps are great for indexing a collection by something other than a number.
  • There should seldom be a need for array mappings.

Sets and bags are stored using only one table for each entity, and one of these tables contains a foreign key for the other:

  1. Sets and Bags Class and Table Model

It will typically be declared like this in code:

public virtual Iesi.Collections.Generic.ISet<Comment> Comments { get; protected set; }

Lists also need only two tables, but they use an additional, unmapped column for storing one entity’s order in its parent’s collection:

  1. One-to-Many/Many-to-One Class and Table Model

A list (and also a bag) is mapped as an IList<T> and the order of its elements is dictated by the list’s indexed column attribute, which must be an integer.

public virtual IList<Post> Posts { get; protected set; }

Many-to-many relations do require an additional mapping table, which does not translate to any class but is used transparently by NHibernate:

  1. Many-to-Many Class and Table Model

These are represented by two set collections, one on each class, only one of them being inverse.

public class User

{

  public virtual Iesi.Collections.Generic.ISet<Blog> Blogs { get; protected set; }

}

public class Blog

{

  public virtual Iesi.Collections.Generic.ISet<User> Users { get; protected set; }

}

Maps also need an additional table for storing the additional property (in the case where the value is a single scalar) or the key to the other endpoint entity (for a one-to-many relation):

  1. Map Class and Table Model

Maps are represented in .NET by IDictionary<TKey, TValue>. The key is always the containing class and the values can either be entities (in which we have a one-to-many or many-to-many relation), components (complex properties) or elements (primitive types).

public virtual IDictionary<String, String> Attributes { get; protected set; }

XML Mappings

Now we get to the bottom of it all. In the beginning, NHibernate only had XML-based mappings. This is still supported and it basically means that, for every entity, there must be a corresponding XML file that describes how the class binds to the data model (you can have a single file for all mappings but this makes it more difficult to find a specific mapping). The XML file must bind the entity class to a database table and declare the properties and associations that it will recognize. One important property that must be present is the one that contains the identifier for the entity instance, the table’s primary key.

By convention, NHibernate’s XML mapping files end with HBM.XML (in any case). The mappings for this example might be (bear with me, an explanation follows shortly):

The way to add IntelliSense to the HBM.XML files is like this:

  1. Download the XML Schema Definition (XSD) file for the configuration section from https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/nhibernate-mapping.xsd.
  2. Open the .HBM.XML file where you have a mapping configuration in Visual Studio.
  3. Go to the Properties window and select the ellipsis (…) button next to Schemas.
  4. Click the Add… button and select the nhibernate-mapping.xsd file that you just downloaded.
  5. Select Use this Schema at the line with target namespace urn:nhibernate-mapping-2.2.

First, the mapping for the User class, which should go in a User.hbm.xml file:

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="User" lazy="true" table="`user`">

    <id name="UserId" column="`user_id`" generator="hilo" />

    <property name="Username" column="`username`" length="10" not-null="true" />

    <property name="Birthday" column="`birthday`" not-null="false" />

    <component name="Details">

      <property name="Fullname" column="`fullname`" length="50" not-null="true" />

      <property name="Email" column="`email`" length="50" not-null="true" />

      <property name="Url" column="`url`" length="50" not-null="false" />

    </component>

    <set cascade="all-delete-orphan" inverse="true" lazy="true" name="Blogs">

      <key column="`user_id`" />

      <one-to-many class="Blog" />

    </set>

  </class>

</hibernate-mapping>

Next, the Blog class (Blog.hbm.xml):

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Blog" lazy="true" table="`blog`">

    <id name="BlogId" column="`blog_id `" generator="hilo" />

    <property name="Name" column="`NAME`" length="50" not-null="true" />

    <property name="Creation" column="`creation`" not-null="true" />

    <property name="PostCount" 

formula="(SELECT COUNT(1) FROM post WHERE post.blog_id = blog_id)" />

    <property name="Picture" column="`PICTURE`" not-null="false" lazy="true">

      <type name="Succinctly.Common.ImageUserType, Succinctly.Common"/>

    </property>

    <many-to-one name="Owner" column="`user_id`" not-null="true" lazy="no-proxy" cascade="save-update"/>

    <list cascade="all-delete-orphan" inverse="true" lazy="true" name="Posts">

      <key column="`blog_id`" />

      <index column="`number`" />

      <one-to-many class="Post" />

    </list>

  </class>

</hibernate-mapping>

The Post (Post.hbm.xml):

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Post" lazy="true" table="`post`">

    <id name="PostId" column="`post_id`" generator="hilo" />

    <property name="Title" column="`title`" length="50" not-null="true" />

    <property name="Timestamp" column="`timestamp`" not-null="true" />

    <property name="Content" type="StringClob" column="`content`" length="2000" not-null="true" lazy="true" />

    <many-to-one name="Blog" column="`blog_id`" not-null="true" lazy="no-proxy" />  

    <set cascade="all" lazy="true" name="Tags" table="`tag`" order-by="`tag`">

      <key column="`post_id`" />

      <element column="`tag`" type="String" length="20" not-null="true" unique="true" />

    </set>

    <set cascade="all-delete-orphan" inverse="true" lazy="true" name="Attachments">

      <key column="`post_id`" />

      <one-to-many class="Attachment" />

    </set>

    <bag cascade="all-delete-orphan" inverse="true" lazy="true" name="Comments">

      <key column="`post_id`" />

      <one-to-many class="Comment" />

    </bag>

  </class>

</hibernate-mapping>

A Post’s Comments (Comment.hbm.xml):

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Comment" lazy="true" table="`comment`">

    <id name="CommentId" column="`comment_id`" generator="hilo" />

    <property name="Timestamp" column="`timestamp`" not-null="true" />

    <property name="Content" type="StringClob" column="`content`" length="2000" not-null="true" lazy="true" />

    <component name="Details">

      <property name="Fullname" column="`fullname`" length="50" not-null="true" />

      <property name="Email" column="`email`" length="50" not-null="true" />

      <property name="Url" column="`url`" length="50" not-null="false" />

    </component>

    <many-to-one name="Post" column="`post_id`" not-null="true" lazy="no-proxy" />

  </class>

</hibernate-mapping>

And, finally, a Post’s Attachments (Attachment.hbm.xml):

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Attachment" lazy="true" table="`attachment`">

    <id name="AttachmentId" column="`attachment_id`" generator="hilo" />

    <property name="Filename" column="`filename`" length="50" not-null="true" />

    <property name="Timestamp" column="`timestamp`" not-null="true" />

    <property name="Contents" type="BinaryBlob" column="`contents`" length="100000" not-null="true" lazy="true" />

    <many-to-one name="Post" column="`post_id`" not-null="true" lazy="no-proxy" />

  </class>

</hibernate-mapping>

Let’s analyze what we have here.

First, all entities have a class mapping declaration. On this declaration we have:

  • The class name
  • The table name where the entity is to be persisted
  • The desired laziness (see Lazy Loading), always lazy in this example

Next, we always need an identifier declaration. In it, we have the following attributes:

  • The property name that contains the identifier
  • The column name that contains the identifier
  • The generator strategy or class (in our examples, always hilo), see section Identifiers

If we need to pass parameters, we can do it like this:

<?xml version="1.0" encoding="utf-8"?>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Attachment" lazy="true" table="`attachment`">

    <id name="AttachmentId" column="`attachment_id`" generator="hilo">

      <param name="sequence">ATTACHMENT_SEQUENCE</param>

    </id>

  <!-- … ->

</hibernate-mapping>

Then, we have scalar property declarations. For each property we need:

  • The property name.
  • The column name.
  • If the column needs to be not-null.
  • The desired lazy setting, see Lazy Loading.
  • Optionally, a SQL formula, in which case the column name will not be used (see the PostCount property of the Blog class).
  • The length of the column, for strings only.
  • In some cases, we have a type declaration (Post.Content, Blog.Picture properties). The Blog.Picture property references by assembly qualified name a custom user type (ImageUserType). This user type allows translating a BLOB column (Byte[]) to a .NET Image, which can be very handy.

We have some complex properties for which we use component declarations. They are useful for storing values that conceptually should be together (see the mappings for the Details property of the User class and the Details property of the Comment class). A component contains:

  • The property name.
  • Optionally, a lazy declaration.
  • One or more scalar property mappings (column, length, not-null).

References come up next. With it, we declare that our entities are associated to the children of another entity. See, for example, the Blog association of the Post entity. A typical mapping includes:

  • The property name.
  • The column name that stores the foreign key.
  • Indication if the foreign key can be not-null, for optional references.
  • The desired lazy option.

Finally, we have collections. These can be of several types (for a discussion, see the Collections section). In this example, we have collections of entities (for example, see the Comments collection of the Post entity) and collections of strings (the Tags collections of the Post class, mapped as bags, lists and sets, respectively). The possible attributes depend on the actual collection but all include:

  • The property name.
  • The kind of collection (list, set or bag, in this example).
  • The lazy option.
  • The column that contains the foreign key, which should generally match the primary key of the main entity.
  • The index column, in the case of lists.
  • The entity class that represents the child side of the one-to-many relation (for collections of entities).
  • The value’s type, length, column, and uniqueness (for collections of scalar values).

If you are to use mapping by XML, pay attention to this: You can either include the HBM.XML files as embedded resources in the assembly where the classes live or you can have them as external files.

If you want to include the resource files as embedded resources, which is probably a good idea because you have less files to deploy, make sure of two things:

  • The project that includes the entity classes should have a base namespace that is identical to the base namespace of these classes (say, for example, Succinctly.Model):

Setting Project Properties

  1. Setting Project Properties
  • The HBM.XML files should be located in the same folder as the classes to which they refer.
  • Each HBM.XML file should be marked as an embedded resource:

Setting Embedded Resources

  1. Setting Embedded Resources
  • For loading mappings from embedded resources, use the AddAssembly method of the Configuration instance or the <mapping assembly> tag in case in XML configuration:

//add an assembly by name

cfg.AddAssembly("Succinctly.Model");

//add an Assembly instance

cfg.AddAssembly(typeof(Blog).Assembly);

<session-factory>

  <!-- … ->

  <mapping assembly="Succinctly.Model" />

</session-factory>

Tip: Beware! Any changes you make to HBM.XML files are not automatically detected by Visual Studio so you will have to build the project explicitly whenever you change anything.

If you prefer instead to have external files:

//add a single file

cfg.AddFile("Blog.hbm.xml");

//add all .HBM.XML files in a directory

cfg.AddDirectory(new DirectoryInfo("."));

Mapping by Code

Mapping by code is new in NHibernate as of version 3.2. Its advantage is that there is no need for additional mapping files and, because it is strongly typed, it is more refactor-friendly. For example, if you change the name of a property, the mapping will reflect that change immediately.

With mapping by code, you typically add a new class for each entity that you wish to map and have that class inherit from NHibernate.Mapping.ByCode.Conformist.ClassMapping<T> where T is the entity class. The following code will result in the exact same mapping as the HBM.XML version. You can see that there are similarities in the code structure and the method names closely resemble the XML tags.

UserMapping class:

public class UserMapping : ClassMapping<User>

{

  public UserMapping()

  {

    this.Table("user");

    this.Lazy(true);

    this.Id(x => x.UserId, x =>

    {

      x.Column("user_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Username, x =>

    {

      x.Column("username");

      x.Length(20);

      x.NotNullable(true);

    });

    this.Property(x => x.Birthday, x =>

    {

      x.Column("birthday");

      x.NotNullable(false);

    });

    this.Component(x => x.Details, x =>

    {

      x.Property(y => y.Fullname, z =>

      {

        z.Column("fullname");

        z.Length(50);

        z.NotNullable(true);

      });

      x.Property(y => y.Email, z =>

      {

        z.Column("email");

        z.Length(50);

        z.NotNullable(true);

      });

      x.Property(y => y.Url, z =>

      {

        z.Column("url");

        z.Length(50);

        z.NotNullable(false);

      });

    });

 

    this.Set(x => x.Blogs, x =>

    {

      x.Key(y =>

      {

        y.Column("user_id");

        y.NotNullable(true);

      });

      x.Cascade(Cascade.All | Cascade.DeleteOrphans);

      x.Inverse(true);

      x.Lazy(CollectionLazy.Lazy);

    }, x =>

    {

      x.OneToMany();

    });

  }

}


Tip: Add a using statement for namespaces NHibernate, NHibernate.Mapping.ByCode.Conformist, NHibernate.Mapping.ByCode, and NHibernate.Type.

BlogMapping class:

public class BlogMapping : ClassMapping<Blog>

{

  public BlogMapping()

  {

    this.Table("blog");

    this.Lazy(true);

 

    this.Id(x => x.BlogId, x =>

    {

      x.Column("blog_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Name, x =>

    {

      x.Column("name");

      x.Length(50);

      x.NotNullable(true);

    });

    this.Property(x => x.Picture, x =>

    {

      x.Column("picture");

      x.NotNullable(false);

      x.Type<ImageUserType>();

      x.Lazy(true);

    });

    this.Property(x => x.Creation, x =>

    {

      x.Column("creation");

      x.NotNullable(true);

    });

    this.Property(x => x.PostCount, x =>

    {

      x.Formula("(SELECT COUNT(1) FROM post WHERE post.blog_id = blog_id)");

    });

 

    this.ManyToOne(x => x.Owner, x =>

    {

      x.Cascade(Cascade.Persist);

      x.Column("user_id");

      x.NotNullable(true);

      x.Lazy(LazyRelation.NoProxy);

    });

 

    this.List(x => x.Posts, x =>

    {

      x.Key(y =>

      {

        y.Column("blog_id");

        y.NotNullable(true);

      });

      x.Index(y =>

      {

        y.Column("number");

      });

      x.Lazy(CollectionLazy.Lazy);

      x.Cascade(Cascade.All | Cascade.DeleteOrphans);

      x.Inverse(true);

    }, x =>

    {

      x.OneToMany();

    });

  }

}

PostMapping class:

public class PostMapping : ClassMapping<Post>

  {

    public PostMapping()

    {

      this.Table("post");

      this.Lazy(true);

 

      this.Id(x => x.PostId, x =>

      {

        x.Column("post_id");

        x.Generator(Generators.HighLow);

      });

 

      this.Property(x => x.Title, x =>

      {

        x.Column("title");

        x.Length(50);

        x.NotNullable(true);

      });

      this.Property(x => x.Timestamp, x =>

      {

        x.Column("timestamp");

        x.NotNullable(true);

      });

      this.Property(x => x.Content, x =>

      {

        x.Column("content");

        x.Length(2000);

        x.NotNullable(true);

        x.Type(NHibernateUtil.StringClob);

      });

 

      this.ManyToOne(x => x.Blog, x =>

      {

        x.Column("blog_id");

        x.NotNullable(true);

        x.Lazy(LazyRelation.NoProxy);

      });

 

      this.Set(x => x.Tags, x =>

      {

        x.Key(y =>

        {

          y.Column("post_id");

          y.NotNullable(true);

        });

        x.Cascade(Cascade.All);

        x.Lazy(CollectionLazy.NoLazy);

        x.Fetch(CollectionFetchMode.Join);

        x.Table("tag");

      }, x =>

      {

        x.Element(y =>

        {

          y.Column("tag");

          y.Length(20);

          y.NotNullable(true);

          y.Unique(true);

        });

      });

      this.Set(x => x.Attachments, x =>

      {

        x.Key(y =>

        {

          y.Column("post_id");

          y.NotNullable(true);

        });

        x.Cascade(Cascade.All | Cascade.DeleteOrphans);

        x.Lazy(CollectionLazy.Lazy);

        x.Inverse(true);

      }, x =>

      {

        x.OneToMany();

      });

      this.Bag(x => x.Comments, x =>

      {

        x.Key(y =>

        {

          y.Column("post_id");

        });

        x.Cascade(Cascade.All | Cascade.DeleteOrphans);

        x.Lazy(CollectionLazy.Lazy);

        x.Inverse(true);

      }, x =>

      {

        x.OneToMany();

      });

    }

  }

CommentMapping class:

public class CommentMapping : ClassMapping<Comment>

{

  public CommentMapping()

  {

    this.Table("comment");

    this.Lazy(true);

 

    this.Id(x => x.CommentId, x =>

    {

      x.Column("comment_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Content, x =>

    {

      x.Column("content");

      x.NotNullable(true);

      x.Length(2000);

      x.Lazy(true);

      x.Type(NHibernateUtil.StringClob);

    });

    this.Property(x => x.Timestamp, x =>

    {

      x.Column("timestamp");

      x.NotNullable(true);

    });

 

    this.Component(x => x.Details, x =>

    {

      x.Property(y => y.Fullname, z =>

      {

        z.Column("fullname");

        z.Length(50);

        z.NotNullable(true);

      });

      x.Property(y => y.Email, z =>

      {

        z.Column("email");

        z.Length(50);

        z.NotNullable(true);

      });

      x.Property(y => y.Url, z =>

      {

        z.Column("url");

        z.Length(50);

        z.NotNullable(false);

      });

    });

 

    this.ManyToOne(x => x.Post, x =>

    {

      x.Column("post_id");

      x.NotNullable(true);

      x.Lazy(LazyRelation.NoProxy);

    });

  }  

}

AttachmentMapping class:

public class AttachmentMapping : ClassMapping<Attachment>

{

  public AttachmentMapping()

  {

    this.Table("attachment");

    this.Lazy(true);

 

    this.Id(x => x.AttachmentId, x =>

    {

      x.Column("attachment_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Filename, x =>

    {

      x.Column("filename");

      x.Length(50);

      x.NotNullable(true);

    });

    this.Property(x => x.Timestamp, x =>

    {

      x.Column("timestamp");

      x.NotNullable(true);

    });

    this.Property(x => x.Contents, x =>

    {

      x.Column("contents");

      x.Length(100000);

      x.Type<BinaryBlobType>();

      x.NotNullable(true);

      x.Lazy(true);

    });

 

    this.ManyToOne(x => x.Post, x =>

    {

      x.Column("post_id");

      x.Lazy(LazyRelation.NoProxy);

      x.NotNullable(true);

    });

  }

}

Notice that for every thing (but table and columns names and raw SQL) there are strongly typed options and enumerations. All options are pretty similar to their HBM.XML counterpart, so moving from one mapping to the other should be straightforward.

For passing parameters to the generator, one would use the following method:

public class AttachmentMapping : ClassMapping<Attachment>

{

  public AttachmentMapping()

  {

    this.Table("attachment");

    this.Lazy(true);

 

    this.Id(x => x.AttachmentId, x =>

    {

      x.Column("attachment_id");

      x.Generator(Generators.HighLow, x => x.Params(new { sequence = "ATTACHMENT_SEQUENCE" } ));

    });

    //…

}

One problem with mapping by code—or more generally speaking, with LINQ expressions—is that you can only access public members. However, NHibernate lets you access both public as well as non-public members. If you want to map non-public classes, you have to use their names, for example:

this.Id("AttachmentId", x =>

{

  //…

});

this.Property("Filename", x =>

{

  //…

});

this.ManyToOne("Post", x =>

{

  //…

});

Now that you have mapping classes, you need to tie them to the Configuration instance. Three ways to do this:

  1. One class mapping at a time (instance of ClassMapping<T>)
  2. A static array of mappings
  3. A dynamically obtained array

Configuration cfg = BuildConfiguration(); //whatever way you like

ModelMapper mapper = new ModelMapper();

//1: one class at a time

mapper.AddMapping<BlogMapping>();

mapper.AddMapping<UserMapping>();

mapper.AddMapping<PostMapping>();

mapper.AddMapping<CommentMapping>();

mapper.AddMapping<AttachmentMapping>();  

//2: or all at once (pick one or the other, not both)

mapper.AddMappings(new Type[] { typeof(BlogMapping), typeof(UserMapping), typeof(PostMapping),

typeof(CommentMapping), typeof(AttachmentMapping) });

//3: or even dynamically found types (pick one or the other, not both)

mapper.AddMappings(typeof(BlogMapping).Assembly.GetTypes().Where(x => x.BaseType.IsGenericType && x.BaseType.GetGenericTypeDefinition() == typeof(ClassMapping<>)));

//code to be executed in all cases.

HbmMapping mappings = mapper.CompileMappingForAllExplicitlyAddedEntities();

cfg.AddDeserializedMapping(mappings, null);

For completeness’ sake, let me tell you that you can use mapping by code without creating a class for each entity to map by using the methods in the ModelMapper class:

mapper.Class<Blog>(ca =>

{

  ca.Table("blog");

  ca.Lazy(true);

  ca.Id(x => x.BlogId, map =>

  {

    map.Column("blog_id");

    map.Generator(Generators.HighLow);

  });

  //…

I won’t include a full mapping here but I think you get the picture. All calls should be identical to what you would have inside a ClassMapping<T>.

Mapping by Attributes

Yet another option is mapping by attributes. With this approach, you decorate your entity classes and properties with attributes that describe how they are mapped to database objects. The advantage with this is that your mapping becomes self-described, but you must add and deploy a reference to the NHibernate.Mapping.Attributes assembly, which is something that POCO purists may not like.

First, you must add a reference to NHibernate.Mapping.Attributes either by NuGet or by downloading the binaries from the SourceForge site at http://sourceforge.net/projects/nhcontrib/files/NHibernate.Mapping.Attributes. Let’s choose NuGet:

Make sure that the project that contains your entities references the NHibernate.Mapping.Attributes assembly. After that, make the following changes to your entities, after adding a reference to NHibernate.Mapping.Attributes namespace:

User class:

[Class(Table = "user", Lazy = true)]

public class User

{

  public User()

  {

    this.Blogs = new Iesi.Collections.Generic.HashedSet<Blog>();

    this.Details = new UserDetail();

  }

 

  [Id(0, Column = "user_id", Name = "UserId")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 UserId { get; protected set; }

 

  [Property(Name = "Username", Column = "username", Length = 20, NotNull = true)]

  public virtual String Username { get; set; }

 

  [ComponentProperty(PropertyName = "Details")]

  public virtual UserDetail Details { get; set; }

 

  [Property(Name = "Birthday", Column = "birthday", NotNull = false)]

  public virtual DateTime? Birthday { get; set; }

 

  [Set(0, Name = "Blogs", Cascade = "all-delete-orphan", Lazy = CollectionLazy.True, Inverse = true

Generic = true)]

  [Key(1, Column = "user_id", NotNull = true)]

  [OneToMany(2, ClassType = typeof(Blog))]

  public virtual Iesi.Collections.Generic.ISet<Blog> Blogs { get; protected set; }

}

Blog class:

[Class(Table = "blog", Lazy = true)]

public class Blog

{

  public Blog()

  {

    this.Posts = new List<Post>();

  }

 

  [Id(0, Column = "blog_id", Name = "BlogId")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 BlogId { get; protected set; }

  [Property(Column = "picture", NotNull = false, TypeType = typeof(ImageUserType), Lazy = true)]

  public virtual Image Picture { get; set; }

  [Property(Name = "PostCount", Formula = "(SELECT COUNT(1) FROM post WHERE post.blog_id = blog_id)")]

  public virtual Int64 PostCount { get; protected set; }

  [ManyToOne(0, Column = "user_id", NotNull = true, Lazy = Laziness.NoProxy, Name = "Owner"

Cascade = "save-update")]

  [Key(1)]

  public virtual User Owner { get; set; }

 

  [Property(Name = "Name", Column = "name", NotNull = true, Length = 50)]

  public virtual String Name { get; set; }

 

  [Property(Name = "Creation", Column = "creation", NotNull = true)]

  public virtual DateTime Creation { get; set; }

 

  [List(0, Name = "Posts", Cascade = "all-delete-orphan", Lazy =  CollectionLazy.True, Inverse = true

Generic = true)]

  [Key(1, Column = "blog_id", NotNull = true)]

  [Index(2, Column = "number")]

  [OneToMany(3, ClassType = typeof(Post))]

  public virtual IList<Post> Posts { get; protected set; }

}

Post class:

[Class(Table = "post", Lazy = true)]

public class Post

{

  public Post()

  {

    this.Tags = new Iesi.Collections.Generic.HashedSet<String>();

    this.Attachments = new Iesi.Collections.Generic.HashedSet<Attachment>();

    this.Comments = new List<Comment>();

  }

 

  [Id(0, Column = "post_id", Name = "PostId")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 PostId { get; protected set; }

 

  [ManyToOne(0, Column = "blog_id", NotNull = true, Lazy = Laziness.NoProxy, Name = "Blog")]

  [Key(1)]

  public virtual Blog Blog { get; set; }

 

  [Property(Name = "Timestamp", Column = "timestamp", NotNull = true)]

  public virtual DateTime Timestamp { get; set; }

 

  [Property(Name = "Title", Column = "title", Length = 50, NotNull = true)]

  public virtual String Title { get; set; }

  [Property(Name = "Content", Column = "content", Length = 2000, NotNull = true, Lazy = true

Type = "StringClob")]

  public virtual String Content { get; set; }

  [Set(0, Name = "Tags", Table = "tag", OrderBy = "tag", Lazy = CollectionLazy.False, 

Cascade = "all", Generic = true)]

  [Key(1, Column = "post_id", Unique = true, NotNull = true)]

  [Element(2, Column = "tag", Length = 20, NotNull = true, Unique = true)]

  public virtual Iesi.Collections.Generic.ISet<String> Tags { get; protected set; }

 

  [Set(0, Name = "Attachments", Inverse = true, Lazy = CollectionLazy.True, Generic = true

Cascade = "all-delete-orphan")]

  [Key(1, Column = "post_id", NotNull = true)]

  [OneToMany(2, ClassType = typeof(Attachment))]

  public virtual Iesi.Collections.Generic.ISet<Attachment> Attachments { get; protected set; }

 

  [Bag(0, Name = "Comments", Inverse = true, Lazy = CollectionLazy.True, Generic = true

Cascade = "all-delete-orphan")]

  [Key(1, Column = "post_id", NotNull = true)]

  [OneToMany(2, ClassType = typeof(Comment))]

  public virtual IList<Comment> Comments { get; protected set; }

}

Comment class:

[Class(Table = "comment", Lazy = true)]

public class Comment

{

  public Comment() 

  {

    this.Details = new UserDetail();

  }

 

  [Id(0, Column = "comment_id", Name = "CommentId")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 CommentId { get; protected set; }

 

  [ComponentProperty(PropertyName = "Details")]

  public virtual UserDetail Details { get; set; }

 

  [Property(Name = "Timestamp", Column = "timestamp", NotNull = true)]

  public virtual DateTime Timestamp { get; set; }

  [Property(Name = "Content", Column = "content", NotNull = true, Length = 2000, Lazy = true

Type = "StringClob")]

  public virtual String Content { get; set; }

 

  [ManyToOne(0, Column = "post_id", NotNull = true, Lazy = Laziness.NoProxy, Name = "Post")]

  [Key(1)]

  public virtual Post Post { get; set; }

}

Attachment class:

[Class(Table = "attachment", Lazy = true)]

public class Attachment  

{

  [Id(0, Column = "attachment_id", Name = "AttachmentId")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 AttachmentId { get; protected set; }

 

  [Property(Name = "Filename", Column = "filename", Length = 50, NotNull = true)]

  public virtual String Filename { get; set; }

 

  [Property(Name = "Contents", Column = "contents", NotNull = true, Length = 100000, 

Type = "BinaryBlob")]

  public virtual Byte[] Contents { get; set; }

 

  [Property(Name = "Timestamp", Column = "timestamp", NotNull = true)]

  public virtual DateTime Timestamp { get; set; }

 

  [ManyToOne(0, Column = "post_id", NotNull = true, Lazy = Laziness.NoProxy, Name = "Post")]

  [Key(1)]

  public virtual Post Post { get; set; }

}

And, finally, the UserDetail class also needs to be mapped (it is the implementation of the Details component of the User and Comment classes):

[Component]

public class UserDetail

{

  [Property(Name = "Url", Column = "url", Length = 50, NotNull = false)]

  public String Url { get; set; }

 

  [Property(Name = "Fullname", Column = "fullname", Length = 50, NotNull = true)]

  public String Fullname { get; set; }

 

  [Property(Name = "Email", Column = "email", Length = 50, NotNull = true)]

  public String Email { get; set; }

}

After that, we need to add this kind of mapping to the NHibernate configuration. Here’s how:

HbmSerializer serializer = new HbmSerializer() { Validate = true };

using (MemoryStream stream = serializer.Serialize(typeof(Blog).Assembly))

{

  cfg.AddInputStream(stream);

}


Tip: Add a reference to the System.IO and NHibernate.Mapping.Attributes namespaces.

For adding parameters to the identifier generator:

[Class(Table = "attachment", Lazy = true)]

public class Attachment  

{

  [Id(0, Column = "attachment_id", Name = "AttachmentId")]

  [Generator(1, Class = "hilo")]

  [Param(Name = "sequence", Content = "ATTACHMENT_SEQUENCE")]

  public virtual Int32 AttachmentId { get; protected set; }

  //

 }

And that’s it. There are two things of which you must be aware when mapping with attributes:

  • For those mappings that need multiple attributes (the collections and the identifiers), you need to set an order on these attributes; that’s what the first number on each attribute declaration is for. See, for example, the Posts collection of the Blog class and its BlogId property.
  • Property attributes need to take the property they are being applied to as their Name.

Mapping Inheritances

Consider the following class hierarchy:

Inheritance Model

  1. Inheritance Model

We have here an abstract concept, a Person, and two concrete representations of it, a NationalCitizen and a ForeignCitizen. Each Person must be one of them. In some countries (like Portugal, for example), there is a National Identity Card, whereas in other countries no such card exists—only a passport and the country of issue.

In object-oriented languages, we have class inheritance, which is something we don’t have with relational databases. So a question arises: How can we store this in a relational database?

In his seminal work Patterns of Enterprise Application Architecture, Martin Fowler described three patterns for persisting class hierarchies in relational databases:

  1. Single Table Inheritance or Table Per Class Hierarchy: A single table is used to represent the entire hierarchy. It contains columns for all mapped properties of all classes and, of course, many of these will be NULL because they will only exist for one particular class. One discriminating column will store a value that will tell NHibernate what class a particular record will map to:

Single Table Inheritance

  1. Single Table Inheritance
  1. Class Table Inheritance or Table Per Class: A table will be used for the columns for all mapped-based class properties, and additional tables will exist for all concrete classes. The additional tables will be linked by foreign keys to the base table:

Class Table Inheritance

  1. Class Table Inheritance
  1. Concrete Table Inheritance or Table Per Concrete Class: One table for each concrete class, each with columns for all mapped properties specific or inherited by each class:

Concrete Table Inheritance

  1. Concrete Table Inheritance

You can see a more detailed explanation of these patterns in Martin’s website at http://martinfowler.com/eaaCatalog/index.html. For now, I’ll leave you with some general thoughts:

  • Single Table Inheritance: When it comes to querying from a base class, this offers the fastest performance because all of the information is contained in a single table. However, if you have a lot of properties in all of the classes, it will be a difficult read and you will have a lot of nullable columns. In all of the concrete classes, all properties must be optional, not mandatory. Since different entities will be stored in the same class and not all share the same columns, they must allow null values.
  • Class Table Inheritance: This offers a good balance between table tidiness and performance. When querying a base class, a LEFT JOIN will be required to join each table from derived classes to the base class table. A record will exist in the base class table and in exactly one of the derived class tables.
  • Concrete Table Inheritance: This will require several UNIONs, one for each table of each derived class because NHibernate does not know beforehand at which table to look. Consequently, you cannot use as the identifier generation pattern one that might generate identical values for any two tables (such as identity or sequence, with a sequence being used for each individual table) because NHibernate would be confused if it finds two records with the same id. Also, you will have the same columns—those from the base class—duplicated on all tables.

As far as NHibernate is concerned, there really isn’t any difference: classes are naturally polymorphic. See the section on Inheritance to learn how to perform queries on class hierarchies.

Let’s start with Single Table Inheritance, by code. First, the base class, Person:

public class PersonMapping : ClassMapping<Person>

{

  public PersonMapping()

  {

    this.Table("person");

    this.Lazy(true);

    this.Discriminator(x =>

    {

      x.Column("class");

      x.NotNullable(true);

    });

    this.Id(x => x.PersonId, x =>

    {

      x.Column("person_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Name, x =>

    {

      x.Column("name");

      x.NotNullable(true);

      x.Length(100);

    });

 

    this.Property(x => x.Gender, x =>

    {

      x.Column("gender");

      x.NotNullable(true);

      x.Type<EnumType<Gender>>();

    });

  }

}

The only thing that’s new is the Discriminator option, which we use to declare the column that will contain the discriminator value for all subclasses.

Next, the mapping for the NationalCitizen class:

public class NationalCitizenMappping : SubclassMapping<NationalCitizen>

{

  public NationalCitizenMappping()

  {

    this.DiscriminatorValue("national_citizen");               

    this.Lazy(true);

 

    this.Property(x => x.NationalIdentityCard, x =>

    {

      x.Column("national_identity_card");

      x.Length(20);         

      x.NotNullable(true);

    });

  }

}

And, finally, the ForeignCitizen:

public class ForeignCitizenMapping : SubclassMapping<ForeignCitizen>

{

  public ForeignCitizenMapping()

  {

    this.DiscriminatorValue("foreign_citizen");

    this.Lazy(true);

 

    this.Property(x => x.Country, x =>

    {

      x.Column("country");

      x.Length(20);

      x.NotNullable(true);

    });

 

    this.Property(x => x.Passport, x =>

    {

      x.Column("passport");

      x.Length(20);

      x.NotNullable(true);

    });

  }

}      

If you would like to map this by XML, here’s one possibility:

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Person" lazy="true" table="`person`" abstract="true">

    <id column="`person_id`" name="PersonId" generator="hilo"/>

    <discriminator column="`class`"/>

    <property name="Name" column="`name`" length="100" not-null="true"/>

    <property name="Gender" column="gender"/>

  </class>

</hibernate-mapping>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" 

xmlns="urn:nhibernate-mapping-2.2">

  <subclass name="NationalCitizen" lazy="true" extends="Person" discriminator-value="national_citizen">

    <property name="NationalIdentityCard" column="`national_identity_card`" length="20" not-null="false"/>

  </subclass>

</hibernate-mapping>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" 

xmlns="urn:nhibernate-mapping-2.2">

  <subclass name="ForeignCitizen" lazy="true" extends="Person" discriminator-value="foreign_citizen">

    <property name="Country" column="`country`" length="20" not-null="false"/>

    <property name="Passport" column="`passport`" length="20" not-null="false"/>

  </subclass>

</hibernate-mapping>

And, finally, if your choice is attributes mapping, here’s how it goes:

[Class(0, Table = "person", Lazy = true, Abstract = true)]

[Discriminator(1, Column = "class")]

public abstract class Person

{

  [Id(0, Name = "PersonId", Column = "person_id")]

  [Generator(1, Class = "hilo")]

  public virtual Int32 PersonId { get; protected set; }

 

  [Property(Name = "Name", Column = "name", Length = 100, NotNull = true)]

  public virtual String Name { get; set; }

 

  [Property(Name = "Gender", Column = "gender", TypeType = typeof(EnumType<Gender>), NotNull = true)]

  public virtual Gender Gender { get; set; }

}

[Subclass(DiscriminatorValue = "national_citizen", ExtendsType = typeof(Person), Lazy = true)]

public class NationalCitizen : Person

{

  [Property(Name = "NationalIdentityCard", Column = "national_identity_card", Length = 50, NotNull = false)]

  public virtual String NationalIdentityCard { get; set; }

}

[Subclass(DiscriminatorValue = "foreign_citizen", ExtendsType = typeof(Person), Lazy = true)]

public class ForeignCitizen : Person

{

  [Property(Name = "Passport", Column = "passport", Length = 50, NotNull = false)]

  public virtual String Passport { get; set; }

 

  [Property(Name = "Country", Column = "country", Length = 50, NotNull = false)]

  public virtual String Country { get; set; }

}

When it comes to querying, a query on the Person class looks like this:

IEnumerable<Person> allPeopleFromLinq = session.Query<Person>().ToList();

Produces this SQL:

SELECT

     person0_.person_id AS person1_2_,

     person0_.name AS name2_,

     person0_.gender AS gender2_,

     person0_.passport AS passport2_,

     person0_.country AS country2_,

     person0_.national_identity_card AS national7_2_,

     person0_.[class] AS class2_2_

FROM

     person person0_

If we want to restrict on a specific type:

IEnumerable<NationalCitizen> nationalCitizensFromLinq = session.Query<NationalCitizen>().ToList();

Will produce this SQL:

SELECT

    nationalci0_.person_id AS person1_2_,

    nationalci0_.name AS name2_,

    nationalci0_.gender AS gender2_,

    nationalci0_.national_identity_card AS national7_2_

FROM

    person nationalci0_

WHERE

    nationalci0_.[class] = 'national_citizen'

Moving on, we have Class Table Inheritance, which is also known in NHibernate jargon as joined subclass because we need to join two tables together to get the class’ values. Here are its loquacious mappings (the mappings for the Person class remain the same, except that we removed the Discriminator call):

public class PersonMapping : ClassMapping<Person>

{

  public PersonMapping()

  {

    this.Table("person");

    this.Lazy(true);

    this.Id(x => x.PersonId, x =>

    {

      x.Column("person_id");

      x.Generator(Generators.HighLow);

    });

 

    this.Property(x => x.Name, x =>

    {

      x.Column("name");

      x.NotNullable(true);

      x.Length(100);

    });

 

    this.Property(x => x.Gender, x =>

    {

      x.Column("gender");

      x.NotNullable(true);

      x.Type<EnumType<Gender>>();

    });

  }

}

public class NationalCitizenMappping : JoinedSubclassMapping<NationalCitizen>

{

  public NationalCitizenMappping()

  {

    this.Table("national_citizen");

    this.Lazy(true);

 

    this.Key(x =>

    {

      x.Column("person_id");

      x.NotNullable(true);

    });

    this.Property(x => x.NationalIdentityCard, x =>

    {

      x.Column("national_identity_card");

      x.Length(20);         

      x.NotNullable(true);

    });

  }

}

For ForeignCitizen:

public class ForeignCitizenMapping : JoinedSubclassMapping<ForeignCitizen>

{

  public ForeignCitizenMapping()

  {

    this.Table("foreign_citizen");

    this.Lazy(true);

 

    this.Key(x =>

    {

      x.Column("person_id");

      x.NotNullable(true);

    });

    this.Property(x => x.Country, x =>

    {

      x.Column("country");

      x.Length(20);

      x.NotNullable(true);

    });

 

    this.Property(x => x.Passport, x =>

    {

      x.Column("passport");

      x.Length(20);

      x.NotNullable(true);

    });

  }

}      

Here, what’s different in the mapping of the subclasses is the introduction of a Key, which we use to tell NHibernate the column to use for joining with the PERSON table.

The XML equivalent would be:

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <class name="Person" lazy="true" table="`PERSON`" abstract="true">

    <id column="`PERSON_ID`" name="PersonId" generator="hilo"/>

    <property name="Name" column="`name`" length="100" not-null="true"/>

    <property name="Gender" column="gender"/>

  </class>

</hibernate-mapping>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <joined-subclass name="NationalCitizen" lazy="true" extends="Person" table="`NATIONAL_CITIZEN`">

    <key column="`PERSON_ID`"/>

    <property name="NationalIdentityCard" column="`national_identity_card`" length="20" not-null="false"/>

  </joined-subclass>

</hibernate-mapping>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" xmlns="urn:nhibernate-mapping-2.2">

  <joined-subclass name="ForeignCitizen" lazy="true" extends="Person" table="`FOREIGN_CITIZEN`">

    <key column="`PERSON_ID`"/>

    <property name="Country" column="`country`" length="20" not-null="false"/>

    <property name="Passport" column="`passport`" length="20" not-null="false"/>

  </joined-subclass>

</hibernate-mapping>

Finally, the attributes version:

[Class(0, Table = "person", Lazy = true, Abstract = true)]

public abstract class Person

{

  //…

}

[JoinedSubclass(0, Table = "national_citizen", ExtendsType = typeof(Person), Lazy = true)]

[Key(1, Column = "person_id")]

public class NationalCitizen : Person

{

  //…

}

[JoinedSubclass(0, Table = "foreign_citizen", ExtendsType = typeof(Person), Lazy = true)]

[Key(1, Column = "person_id")]

public class ForeignCitizen : Person

{

  //…

}

A query for the base class produces the following SQL, joining all tables together:

SELECT

    person0_.person_id AS person1_6_,

    person0_.name AS name6_,

    person0_.gender AS gender6_,

    person0_1_.passport AS passport11_,

    person0_1_.country AS country11_,

    person0_2_.national_identity_card AS national2_12_,

    CASE

        WHEN person0_1_.person_id IS NOT NULL THEN 1

        WHEN person0_2_.person_id IS NOT NULL THEN 2

        WHEN person0_.person_id IS NOT NULL THEN 0

    END AS clazz_

FROM

    person person0_

LEFT OUTER JOIN

    foreign_citizen person0_1_

        ON person0_.person_id = person0_1_.person_id

LEFT OUTER JOIN

    national_citizen person0_2_

        ON person0_.person_id = person0_2_.person_id

And one for a specific class:

SELECT

     nationalci0_.person_id AS person1_2_,

     nationalci0_1_.name AS name2_,

     nationalci0_1_.gender AS gender2_,

     nationalci0_.national_identity_card AS national2_8_

FROM

     national_citizen nationalci0_

INNER JOIN

     person nationalci0_1_

         ON nationalci0_.person_id = nationalci0_1_.person_id

Finally, the last mapping strategy, Concrete Table Inheritance or union-subclass. The mappings, using the loquacious API:

public class NationalCitizenMappping : UnionSubclassMapping<NationalCitizen>

{

  public NationalCitizenMappping()

  {

    this.Table("national_citizen");

    this.Lazy(true);

    //…

  }

}

public class ForeignCitizenMappping : UnionSubclassMapping<ForeignCitizen>

{

  public ForeignCitizenMappping()

  {

    this.Table("foreign_citizen");

    this.Lazy(true);

    //…

  }

}


Tip: The mapping for Person is exactly the same as for the Class Table Inheritance.

As you can see, the only difference is that the Key entry is now gone. That’s it.

Also in XML:

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" 

xmlns="urn:nhibernate-mapping-2.2">

  <union-subclass name="NationalCitizen" lazy="true" extends="Person" table="`NATIONAL_CITIZEN`">

    <!---->

  </union-subclass>

</hibernate-mapping>

<hibernate-mapping namespace="Succinctly.Model" assembly="Succinctly.Model" 

xmlns="urn:nhibernate-mapping-2.2">

  <union-subclass name="ForeignCitizen" lazy="true" extends="Person" table="`FOREIGN_CITIZEN`">

    <!---->

  </union-subclass>

</hibernate-mapping>

And in attributes:

[UnionSubclass(0, Table = "national_citizen", ExtendsType = typeof(Person), Lazy = true)]

public class NationalCitizen : Person

{

  //…

}

[UnionSubclass(0, Table = "foreign_citizen", ExtendsType = typeof(Person), Lazy = true)]

public class ForeignCitizen : Person

{

  //…

}

At the end of this section, let’s see what the SQL for the base Person class looks like when using this strategy:

SELECT

    person0_.person_id AS person1_6_,

    person0_.name AS name6_,

    person0_.gender AS gender6_,

    person0_.passport AS passport11_,

    person0_.country AS country11_,

    person0_.national_identity_card AS national1_12_,

    person0_.clazz_ AS clazz_

FROM

    ( SELECT

        person_id,

        name,

        gender,

        passport,

        country,

        null AS national_identity_card,

        1 AS clazz_

    FROM

        foreign_citizen

    UNION

    ALL SELECT

        person_id,

        name,

        gender,

        null AS passport,

        null AS country,

        national_identity_card,

        2 AS clazz_

    FROM

        national_citizen

) person0_

And a query for a particular class, in this case, NationalCitizen will produce the following SQL:

SELECT

     nationalci0_.person_id AS person1_6_,

     nationalci0_.name AS name6_,

     nationalci0_.gender AS gender6_,

     nationalci0_.national_identity_card AS national1_12_

FROM

     national_citizen nationalci0_

Which One Shall I Choose?

We have seen a lot of things in this chapter. For mapping APIs, I risk offering my opinion: choose mapping by code. It is more readable and easy to maintain than HBM.XML, and it is strongly typed, meaning that, if you change some property, you will automatically refactor the mapping and won’t need to bother to explicitly compile your project because, since it’s code, Visual Studio will do it whenever necessary. Attributes require a reference to a NHibernate-specific assembly and are more difficult to change than mapping by code.

In the end, with whatever mapping you choose, here is the resulting database model:

Database Model for the Blogging System

  1. Database Model for the Blogging System

For mapping inheritances, it is a decision worth thinking over, as there is no direct answer. But I am in favor of Class Table Inheritance because there is no duplication of columns for each concrete class and because each table only has a few columns.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.