Home Application-development NHibernate 3.0 Cookbook

NHibernate 3.0 Cookbook

By Jason Dentler
books-svg-icon Book
Subscription
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
Subscription
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Models and Mappings
About this book

NHibernate is an innovative, flexible, scalable, and feature-complete open source project for data access. Although it sounds like an easy task to build and maintain database applications, it can be challenging to get beyond the basics and develop applications that meet your needs perfectly.

The NHibernate Cookbook explains each feature of NHibernate 3.0 in detail through example recipes that you can quickly apply to your applications. Set yourself free from stored procedures and inline SQL. Quite simply, if you build .NET applications that use databases, this book is for you.

The book will take you from the absolute basics of NHibernate through its most advanced features and beyond, showing you how to take full advantage of each concept to quickly create amazing database applications. Beginners will learn several techniques for each of the 4 core NHibernate tasks – mapping, configuration, session & transaction management, and querying – and which techniques fit best with various types of applications. In short, you will be able to build an application using NHibernate. Intermediate level readers will learn how to best implement enterprise application architecture patterns using NHibernate, leading to clean, easy-to-understand code, and increased productivity. In addition to new v3.0 features, advanced readers will learn creative ways to extend NHibernate core, as well as techniques using the NHibernate search, shards, spatial, and validation projects.

Publication date:
October 2010
Publisher
Packt
Pages
328
ISBN
9781849513043

 

Chapter 1. Models and Mappings

In this chapter, we will cover the following topics:

  • Mapping a class with XML

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Bidirectional one-to-many class relationships

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Introduction


NHibernate is a popular, mature, open source object / relational mapper (ORM) based on Java's Hibernate project. ORMs, such as LINQ to SQL, Entity Framework, and NHibernate, translate between the database's relational model of tables, columns, and keys to the application's object model of classes and properties.

The NHibernate homepage, http://NHForge.org, contains blog posts, a wiki, the complete reference documentation, and a bug tracker. Support is available through the very active nhusers Google group at http://groups.google.com/group/nhusers. The NHibernate source code is hosted on SourceForge at http://sourceforge.net/projects/nhibernate/. Precompiled binaries of NHibernate releases are also available on SourceForge.

 

Mapping a class with XML


The suggested first step in any new NHibernate application is mapping the model. In this first example, I'll show you how to map a simple product class.

Getting ready

Before we begin mapping, let's get our Visual Studio solution set up. Follow these steps to set up your solution with NHibernate binaries and schemas.

  1. Download the NHibernate 3.0 binaries from SourceForge at http://sourceforge.net/projects/nhibernate/files/. The filename should be NHibernate-3.0.0.GA-bin.zip, perhaps with a slightly different version number.

  2. In Visual Studio, create a new C# class library project named Eg.Core with a directory for the solution named Cookbook.

  3. Delete the Class1.cs file.

  4. In the Solution Explorer, right-click on the Cookbook solution and select Open Folder in Windows Explorer. This will open an Explorer window to the Cookbook directory.

  5. Inside the Cookbook folder, create a new folder named Lib.

  6. Extract the following files from the NHibernate 3 binaries ZIP to the Lib folder:

    • All files in the Required_Bin folder

    • All files in the Required_For_LazyLoading\Castle folder

  7. Back in Visual Studio, right-click on the Solution, and select Add | New Solution Folder.

  8. Name the folder Schema.

  9. Right-click on the Schema folder, and select Add | Existing Item.

  10. Browse to the Lib folder, and add two files: nhibernate-configuration.xsd and nhibernate-mapping.xsd. When the files open in the editor, just close them.

  11. Your solution appears as shown in the next screenshot:

How to do it...

Now, let's start by creating our Product class with the following steps:

  1. In Eg.Core, create a new C# class named Entity with the following code:

    using System;
    
    namespace Eg.Core
    {
      public abstract class Entity
      {
    
        public virtual Guid Id { get; protected set; }
    
      }
    
    }
  2. Create a new class named Product with the following code:

    using System;
    
    namespace Eg.Core
    {
      public class Product : Entity 
      {
    
        public virtual string Name { get; set; }
        public virtual string Description { get; set; }
        public virtual decimal UnitPrice { get; set; }
    
      }
    }
  3. Build your application and correct any compilation errors.

Next, let's create an NHibernate mapping for our product class. Follow these steps:

  1. In the Solution Explorer window, right-click on your project, and choose Add | New Item.

  2. Choose the Data category on the left pane.

  3. Choose XML file on the right pane.

  4. Name the file Product.hbm.xml.

  5. In the Solution Explorer, right-click on Product.hbm.xml, and choose Properties.

  6. Change Build Action from Content to Embedded Resource.

  7. In the editor, enter the following XML in Product.hbm.xml. Let the IntelliSense guide you.

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Eg.Core"
        namespace="Eg.Core">
      <class name="Product">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="Name" not-null="true" />
        <property name="Description" />
        <property name="UnitPrice" not-null="true" 
          type="Currency" />
      </class>
    </hibernate-mapping>

How it works...

In this recipe, we begin by creating our model. The model is the collection of classes that will be persisted or stored in the database. A persistent class is any class that will be persisted. An entity class is a persistent class with an ID. An instance of an entity class is called an entity. So far, our model only contains the Product entity class. We will expand on this model over the next few recipes.

Notice that our Product class looks just like any other Plain Old CLR Object (POCO) class. One of the strongly held design decisions in NHibernate is that all entity classes should be persistence ignorant, that is, they should not know about, or be dependent on NHibernate.

Let's examine the Id property a little closer. The Id property of each Product instance will contain the primary key value from the database. In NHibernate, this is named the persistent object identifier (POID). Just as the primary key value uniquely identifies a row in a database table, the POID will uniquely identify an entity in memory.

If you are new to NHibernate, this protected setter may look strange to you.

    public virtual Guid Id { get; protected set; }

This is a shorthand way to limit access to the Id property. Code outside of the Product class is unable to change the value of the Id property. However, NHibernate sets properties using highly optimized reflection, ignoring the protected restriction. This keeps your application from inadvertently altering this value.

Next, we create our mapping for the Product entity class. Visual Studio uses the nhibernate-mapping.xsd schema to provide IntelliSense while completing this mapping. As a general rule, all NHibernate mapping files end with a .hbm.xml extension, and have a build action of Embedded Resource. NHibernate searches through the embedded resources in your assembly, loading each one with this extension.

Note

One of the most common mistakes in mapping is forgetting to set the build action to Embedded Resource. This leads to the "No Persister for class" MappingException.

Let's break down this XML mapping. Every XML mapping document contains a single hibernate-mapping element. The xmlns attribute sets the XML namespace. Along with the schema in our Schema folder, Visual Studio uses this to enable IntelliSense inside NHibernate mappings.

The assembly attribute tells NHibernate which assembly, by default, contains our types. Similarly, the namespace attribute sets the default .NET namespace types in this mapping file. Together, they allow us to use the simple name Product instead of the full assembly qualified name of Eg.Core.Product, Eg.Core. Inside the hibernate-mapping element, we have a class element. The name attribute tells NHibernate that this class element defines the mapping for our entity class Product.

The Id element defines the POID. The name attribute refers to the Id property of our Product class. It is case-sensitive, just as in the C# language.

The generator element defines how NHibernate will generate POIDs. In this case, we've told NHibernate to use the guid.comb algorithm. Several other options exist.

The property elements define properties on our Product class. Each name attribute matches the name of a property on our Product class. By default, NHibernate allows null values. Adding not-null="true" tells NHibernate to disallow null values.

Tip

Avoid redundant mappings

In general, it's best to keep your mappings as short and concise as possible. NHibernate intelligently scans your model and combines this knowledge with the information provided in the mapping. In most cases, specifying the types of properties in your mappings only creates redundancies that must be maintained. The default table name matches the class name, and each column name matches the corresponding property by default. It's not necessary to specify this information again. Similarly, you should avoid setting an attribute in your mapping when it matches an NHibernate default. For example, adding not-null="false" to each of your properties is redundant, and makes your mapping difficult to read.

With this mapping, the Microsoft SQL Server database table used to store our Product entities appears as shown in the next screenshot. It may differ slightly for other databases.

There's more...

There are three main approaches to begin developing an NHibernate application.

  • With the model-first approach, the path taken in this book, we create our model, map the model, configure NHibernate, and finally generate our database tables from the model and mappings.

  • The configuration-first approach differs slightly. We build our configuration first, then add each entity class and mapping one at a time. This is a more iterative approach to the model-first approach. Again, the database is generated from the model and mappings.

  • The database-first approach is only suggested when sharing an existing database with another application. Depending on the database design, this usually requires some advanced mapping techniques. Many NHibernate beginners travel down this path for fresh database applications and end up with mapping and modelling problems well beyond their experience level.

What happens to these mappings?

When it loads, NHibernate will deserialize each of our XML mappings into a graph of hibernate mapping objects. NHibernate combines this data with metadata from the entity classes to create mapping metadata. This mapping metadata contains everything NHibernate must know about our model.

Surrogate keys and natural IDs

A natural key is an ID that has semantic meaning or business value. It "means something" to people in the real world. A surrogate key is a system generated ID that has no semantic meaning. It is just a value that uniquely identifies data in a database table. NHibernate strongly encourages the use of surrogate keys. There are two reasons for this.

First, the use of natural keys inevitably leads to the use of composite keys . Composite keys are multi-field keys composed of the natural keys of other objects. Let's examine the model of a university's course schedule. The natural key for your term or semester entity may be Fall 2010. The natural key for the Biology department may be BIOL. The natural key for an introductory Biology course would be BIOL 101, a composite of the department's natural key and a course number, each stored in a separate field, with proper foreign keys. The natural key for a section or course offering would be the combination of the natural ids from the term, the course, and a section number. You would have a key composed of four distinct pieces of information. The size of the key grows exponentially with each layer. This quickly leads to an incredible amount of complexity.

Second, because natural keys have real-world meaning, they must be allowed to change with the real world. Let's assume you have an Account class with a UserName property. While this may be unique, it's not a good candidate for use as a key. Suppose usernames are composed of the first initial followed by the last name. When someone changes their name, you'll have to update several foreign keys in your database. If, instead, you use an integer with no meaning for the POID, you only have to update a single UserName field.

However, UserName would be a great candidate for a natural id. A natural id is a property or set of properties that is unique and not null. Essentially, it is the natural key of an entity, though it is not used as the primary key. The mapping for a natural id appears as shown in the following code:

<natural-id mutable="true">
  <property name="UserName" not-null="true" />
</natural-id>

The natural-id element has one attribute: mutable. The default value is false, meaning that the property or properties contained in this natural id are immutable, or constant. In our case, we want to allow our application to change the UserName of an account from time-to-time, so we set mutable to true. In addition to some subtle improvements in caching, this natural id will create a unique database index on UserName.

ID generator selection

NHibernate offers many options for generating POIDs. Some are better than others, and generally fall under these four categories:

The assigned generator requires an application to assign an identifier before an object is persisted. This is typical when natural keys are used.

Non-insert POID generators are the best option for new applications. These generators allow NHibernate to assign an identity to a persistent object without writing the object's data to the database, allowing NHibernate to delay writing until the business transaction is complete, reducing round trips to the database. The following POID generators fit in this category:

  • hilo generates an integer using the Hi/Lo algorithm, where an entire range of integers is reserved and used as needed. Once they've all been used, another range is reserved. Because the identity reservation is managed using a database table, this POID generator is safe for use in a database cluster, web farm, client, or server application, or other scenarios where a single database is shared by multiple applications or multiple instances of an application.

  • guid generates a GUID by calling System.Guid.NewGuid(). All of the GUID-based generators are safe for use in a shared-database environment.

  • guid.comb combines 10 bytes of a seemingly-random GUID, with six bytes representing the current date and time to form a new GUID. This algorithm reduces index fragmentation while maintaining high performance.

  • guid.native gets a GUID from the database. Each generation requires a round-trip to the database.

  • uuid.hex generates a GUID and stores it as a human-readable string of 32 hex digits with or without dashes.

  • uuid.string generates a GUID, converts each of the GUID's 16 bytes to the binary equivalent character, and stores the resulting 16 characters as a string. This is not human readable.

  • counter (also known as vm) is a simple incrementing integer. It's initialized from the system clock and counts up. It's not appropriate for shared-database scenarios.

  • increment is also a simple incrementing integer. It's initialized by fetching the maximum primary key value from the database at start-up. It's not appropriate for shared-database scenarios.

  • sequence fetches a single new ID from a database that supports named sequences, such as Oracle, DB2, and PostgreSQL. Each generation requires a round trip to the database. seqhilo provides better performance.

  • seqhilo combines the Hi/Lo algorithm and sequences to provide better performance over the sequence generator.

  • foreign simply copies keys across a one-to-one relationship. For example, if you have contact and customer associated by a one-to-one relationship, a foreign generator on customer would copy the ID from the matching contact.

Post-insert POID generators require data to be persisted to the database for an ID to be generated. This alters the behavior of NHibernate in very subtle ways and disables some performance features. As such, use of these POID generators is strongly discouraged! They should only be used with existing databases where other applications rely on this behavior.

  • identity returns a database-generated ID.

  • select performs a SELECT to fetch the ID from the row after the insert. It uses the natural id to find the correct row.

  • sequence-identity returns a database-generated ID for databases that support named sequences.

  • trigger-identity returns an ID generated by a database trigger.

Finally, the native generator maps to a different POID generator, depending on the database product. For Microsoft SQL Server, DB2, Informix, MySQL, PostgreSQL, SQLite, and Sybase, it is equivalent to identity. For Oracle and Firebird, it's the same as sequence. On Ingres, it's hilo.

See also

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Creating class hierarchy mappings


It's common to have an inheritance hierarchy of subclasses. In this example, I will show you one method for mapping inheritance with NHibernate, called table-per-class hierarchy.

Getting ready

Complete the previous Mapping a class with XML example.

How to do it...

  1. Create a new class named Book with the following code:

    namespace Eg.Core
    {
      public class Book : Product
      {
    
        public virtual string ISBN { get; set; }
        public virtual string Author { get; set; }
    
      }
    }
  2. Create a new class named Movie with the following code:

    namespace Eg.Core
    {
      public class Movie : Product 
      {
    
        public virtual string Director { get; set; }
    
      }
    }
  3. Change the Product mapping to match the XML shown in the following code:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Eg.Core"
        namespace="Eg.Core">
      <class name="Product">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <discriminator column="ProductType" />
        <natural-id mutable="true">
          <property name="Name" not-null="true" />
        </natural-id>
        <property name="Description" />
        <property name="UnitPrice" not-null="true" />
      </class>
    </hibernate-mapping>
  4. Create a new embedded resource named Book.hbm.xml with the following XML:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Eg.Core"
        namespace="Eg.Core">
      <subclass name="Book" extends="Product">
        <property name="Author"/>
        <property name="ISBN"/>
      </subclass>
    </hibernate-mapping>
  5. Create another embedded resource named Movie.hbm.xml with the next XML:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Eg.Core"
        namespace="Eg.Core">
      <subclass name="Movie" extends="Product">
        <property name="Director" />
      </subclass>
    </hibernate-mapping>

How it works...

In this example, we've mapped a table-per-class hierarchy, meaning data for our entire hierarchy is stored in a single table, as shown in the next screenshot:

NHibernate uses a discriminator column, ProductType in this case, to distinguish among products, books, and movies. By default, the discriminator contains the class name. In this example, that would be Eg.Core.Product, Eg.Core.Book, or Eg.Core.Movie. These defaults can be overridden in the mappings by using a discriminator-value attribute on our class and subclass elements.

In our Book.hbm.xml mapping, we've defined Book as a subclass of Product with Author and ISBN properties. In our Movie.hbm.xml mapping, we've defined Movie as a subclass of Product with a Director property.

With table-per-class-hierarchy, we cannot define any of our subclass properties as not-null="true", because this would create a not-null constraint on those fields. For instance, if we set up the Director property as not null, we wouldn't be able to insert Product or Book instances, because they don't define a Director property. If this is required, use one of the hierarchy mapping strategies listed next.

There's more...

Java refugees may recognize the extends attribute, as extends is the Java keyword used to declare class inheritance. NHibernate first came to life as a port of Java's Hibernate ORM.

Table-per-class hierarchy is the suggested method for mapping class hierarchies, but NHibernate always gives us other options. However, mixing these options within the same class hierarchy is discouraged, and only works in very limited circumstances.

Table per class

In table-per-class mappings, properties of the base class (Product) are stored in a shared table, while each subclass gets its own table for the subclass properties.

Table per subclass uses the joined-subclass element, which requires a key element to name the primary key column. As the name implies, NHibernate will use a join to query for this data. Also, notice that our Product table doesn't contain a ProductType column. Only table-per-class hierarchy uses discriminators. Using table-per-class, our Movie mapping will appear as the following code:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg.Core"
    namespace="Eg.Core">
  <joined-subclass name="Movie" extends="Product">
    <key column="Id" />
    <property name="Director" />
  </joined-subclass>
</hibernate-mapping>

Table per concrete class

In table-per-concrete-class mappings, each class gets its own table containing columns for all properties of the class and the base class, as shown in the next screenshot:

There is no duplication of data. That is, data from a Book instance is only written to the Book table, not the Product table. To fetch Product data, NHibernate will use unions to query all three tables. Using table-per-concrete-class, our Movie mapping will appear as shown in the following code:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="Eg.Core"
    namespace="Eg.Core">
  <union-subclass name="Movie" extends="Product">
    <property name="Director" />
  </union-subclass>
</hibernate-mapping>

See also

  • Mapping a class with XML

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Mapping a one-to-many relationship


It's usually necessary to relate one entity to another. In this example, I'll show you how to map a one-to-many relationship between Movies and a new entity class, ActorRoles.

Getting ready

Complete the previous Creating class hierarchy mappings example.

How to do it...

  1. Create a new class named ActorRole with the following code:

    namespace Eg.Core
    {
      public class ActorRole : Entity 
      {
    
        public virtual string Actor { get; set; }
        public virtual string Role { get; set; }
    
      }
    }
  2. Create an embedded resource mapping for ActorRole with the following XML:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="Eg.Core"
        namespace="Eg.Core">
      <class name="ActorRole">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="Actor" not-null="true" />
        <property name="Role" not-null="true" />
      </class>
    </hibernate-mapping>
  3. Add this Actors property to the Movie class:

    using System.Collections.Generic;
    
    namespace Eg.Core
    {
      public class Movie : Product 
      {
    
        public virtual string Director { get; set; }
        public virtual IList<ActorRole> Actors { get; set; }
    
      }
    }
  4. Add the following list element to our Movie mapping:

    <subclass name="Movie" extends="Product">
      <property name="Director" />
      <list name="Actors" cascade="all-delete-orphan">
        <key column="MovieId" />
        <index column="ActorIndex" />
        <one-to-many class="ActorRole"/>
      </list>
    </subclass>

How it works...

Our ActorRole mapping is simple. Check out Mapping a class with XML for more information. ActorRole isn't part of our Product hierarchy. In the database, it gets a table of its own, as shown in the next screenshot:

As expected, the ActorRole table has fields for the Id, Actor, and Role properties. The MovieId and ActorIndex columns come from the mapping of our Actors list on Movie, not the ActorRole mapping.

The Actors property uses an IList collection. Another strong design choice with NHibernate, and a good programming practice in general, is the liberal use of interfaces. This allows NHibernate to use its own list implementation to support lazy loading, discussed later in this recipe.

In our Movie mapping, the Actors property is mapped with the list element. To associate an ActorRole with a Movie in the database, we store the Movie's Id with each ActorRole. The key element tells NHibernate to store this in a column named MovieId.

We've defined Actors as a list, which implies that order is significant. Actors in leading roles get top billing. Our index element defines the ActorIndex column to store the position of each element in the list. Finally, we tell NHibernate that Actors is a collection of ActorRoles with <one-to-many class="ActorRole" />.

The all-delete-orphan value of the cascade attribute tells NHibernate to save the associated ActorRole objects automatically when it saves a Movie, and delete them when it deletes a Movie.

There's more...

There are a few items to discuss with this recipe.

Lazy loading collections

To improve application performance, NHibernate supports lazy loading. In short, data isn't loaded from the database until it is required by the application. Let's look at the steps NHibernate will use when our application fetches a movie from the database:

  1. NHibernate fetches Id, Name, Description, UnitPrice, and Director data from the database for a Movie with a given Id. Notice that we do not load the Actors data. NHibernate uses the following SQL query:

    select 
      movie0_.Id as Id1_, 
      movie0_.Name as Name1_, 
      movie0_.Description as Descript4_1_, 
      movie0_.UnitPrice as UnitPrice1_, 
      movie0_.Director as Director1_ 
    from Product movie0_ 
    where 
      movie0_.ProductType='Eg.Core.Movie' and 
      movie0_.Id = 'a2c42861-9ff0-4546-85c1-9db700d6175e'
  2. NHibernate creates an instance of the Movie object.

  3. NHibernate sets the Id, Name, Description, UnitPrice, and Director properties of the Movie object with the data from the database.

  4. NHibernate creates a special lazy loading object that implements IList<ActorRole>, and sets the Actors property of the Movie object. It is not a List<ActorRoles>, but rather a separate, NHibernate-specific implementation of the IList<ActorRole> interface.

  5. NHibernate returns the Movie object to our application.

Then, suppose our application contains the following code. Remember, we haven't loaded any ActorRole data.

foreach (var actor in movie.Actors)
  Console.WriteLine(actor.Actor);

The first time we enumerate the collection, the lazy loading object is initialized. It loads the associated ActorRole data from the database with a query as shown:

SELECT 
  actors0_.MovieId as MovieId1_, 
  actors0_.Id as Id1_, 
  actors0_.ActorIndex as ActorIndex1_, 
  actors0_.Id as Id0_0_, 
  actors0_.Actor as Actor0_0_, 
  actors0_.Role as Role0_0_ 
FROM ActorRole actors0_ 
WHERE 
  actors0_.MovieId= 'a2c42861-9ff0-4546-85c1-9db700d6175e'

We can disable lazy loading of a collection by adding the attribute lazy="false" to the list element of our mapping.

Lazy loading proxies

In other circumstances, NHibernate also supports lazy loading through the use of proxy objects. Suppose our ActorRole class had a reference back to Movie, like the following code:

public class ActorRole : Entity
{

  public virtual string Actor { get; set; }
  public virtual string Role { get; set; }
  public virtual Movie Movie { get; set; }

}

If we fetch an ActorRole from the database, NHibernate builds the ActorRole object as we would expect, but it only knows the Id of the associated Movie. It won't have all the data necessary to construct the entire Movie object. Instead, it will create a proxy object to represent the Movie and enable lazy loading.

We can, of course, access the Id of this Movie proxy without loading the movie's data. If we access any other property or method on the proxy, NHibernate will immediately fetch all the data for this movie. Loading this data is completely transparent to the application. The proxy object behaves exactly like a real Movie entity.

This proxy object is a subclass of Movie. In order to subclass Movie and intercept these calls to trigger lazy loading, NHibernate requires a few things from our Movie class.

  • Movie cannot be a sealed class.

  • Movie must have a protected or public constructor without parameters.

  • All public members of Movie must be virtual. This includes methods.

NHibernate gives us several choices for the creation of these proxy objects. The traditional choice of NHibernate proxy framework is DynamicProxy, part of the Castle stack of projects. Additionally, NHibernate includes support for LinFu and Spring.NET, and allows you to build your own.

If we specify lazy="false" on the class element of our Movie mapping, we can disable this behavior. NHibernate will never create a proxy of Movie. This will force NHibernate to immediately load the associated movie's data any time it loads an ActorRole. Loading data unnecessarily like this can quickly kill the performance of your application, and should only be used in very specific, well-considered circumstances.

Collections

NHibernate supports several collection types. The most common types are as follows:

 

Bag

Set

List

Map

Allows Duplicates

Yes

No

Yes

Keys must be unique. Values may be duplicated.

Order is significant

No

No

Yes

No

Type

IList

Iesi.Collections.ISet

IList

IDictionary

All collections may also use the ICollection type, or a custom collection type implementing NHibernate.UserType.IUserCollectionType. Only bag and set may be used in bidirectional relationships.

Bags

A bag collection allows duplicates, and implies that order is not important. Let's talk about a bag of ActorRole entities. The bag may contain actor role 1, actor role 2, actor role 3, actor role 1, actor role 4, and actor role 1. A typical bag mapping appears as shown in the following code:

<bag name="Actors">
  <key column="MovieId"/>
  <one-to-many class="ActorRole"/>
</bag>

The corresponding Actors property may be an IList or ICollection, or even an IEnumerable.

There is no way to identify an individual entry in the bag distinctly with a SQL statement. For example, there is no way to construct a SQL statement to delete just the second entry of actor role 1 from the bag. The SQL statement delete from Actors where ActorRoleId='1' will delete all three entries. When an entry is removed, and the updated bag is persisted, the rows representing the old bag contents are deleted, and then entire bag contents are reinserted. For especially large bags, this can create performance issues.

To counter this issue, NHibernate also provides an idBag where each entry in the bag is assigned an ID by one of the POID generators. This allows NHibernate to uniquely address each bag entry with queries like delete from Actors where ActorRoleBagId='2'.

The mapping for an idBag looks like the following code:

<idBag name="Actors">
  <collection-id column="ActorRoleBagId" type="Int64">
    <generator class="hilo" />
  </collection-id>
  <key column="MovieId"/>
  <one-to-many class="ActorRole"/>
</idBag>

Lists

A list collection also allows duplicates, but unlike a bag, the order is significant. Our list may contain actor role 1 at index 0, actor role 2 at index 1, actor role 3 at index 2, actor role 1 at index 3, actor role 4 at index 4, and actor role 1 at index 5. A typical list mapping looks like the following code:

<list name="Actors">
  <key column="MovieId" />
  <list-index column="ActorRoleIndex" />
  <one-to-many class="ActorRole"/>
</list>

The corresponding Actors property should be an IList. Because NHibernate maintains order with the ActorRoleIndex column, it can also uniquely identify individual list entries. However, because it maintains order, it also means that these indexes must be reset whenever the list contents change. For example, suppose we have a list of six actor roles and we remove the third actor role. NHibernate updates the ActorRoleIndex of each list entry.

Sets

A set collection does not allow duplicates, and the order of a set is not important. In my applications, this is the most common collection type. A set may contain actor role 1, actor role 3, actor role 2, and actor role 4. An attempt to add actor role 1 to the set again will fail. A typical set mapping appears as shown in the following code:

<set name="Actors">
  <key column="MovieId" />
  <one-to-many class="ActorRole"/>
</set>

The corresponding Actors property should be an ISet from Iesi.Collections.dll. Currently, NHibernate does not directly support the ISet interface included in the .NET Framework 4.

An attempt to add an item to an uninitialized lazy-loaded set collection will cause the set to be loaded from the database. This is necessary to ensure uniqueness in the collection. To ensure proper uniqueness in a set, you should override the Equals and GetHashCode methods, as shown in the next recipe.

Map

Map is another term that crossed over when NHibernate was ported from Java. In .NET, it's known as a dictionary. Each collection entry is a key or value pair. Keys must be unique. Values may not be unique.

<map name="Actors" >
  <key column="MovieId" />
  <map-key column="Role" type="string" />
  <element column="Actor" type="string"/>
</map>

As you may have guessed, the corresponding Actors property must be an IDictionary<string, string>, where the key is the name of the movie role, and the value is the actor's name. You are not limited to basic data types as shown here. NHibernate also allows entities for keys and values as shown in the following code:

<map name="SomeProperty">
  <key column="Id" />
  <index-many-to-many class="KeyEntity"/>
  <many-to-many class="ValueEntity" />
</map>

See also

  • Mapping a class with XML

  • Creating class hierarchy Mappings

  • Setting up a base entity class

  • Bidirectional one-to-many class relationships

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Setting up a base entity class


In this recipe, I'll show you how to set up a base class to use for your entities.

Getting ready

Complete the previous three recipes.

How to do it...

  1. In Entity.cs, use the following code for the Entity class:

    public abstract class Entity<TId>
    {
    
      public virtual TId Id { get; protected set; }
    
      public override bool Equals(object obj)
      {
        return Equals(obj as Entity<TId>);
      }
    
      private static bool IsTransient(Entity<TId> obj)
      {
        return obj != null &&
               Equals(obj.Id, default(TId));
      }
    
      private Type GetUnproxiedType()
      {
        return GetType();
      }
    
      public virtual bool Equals(Entity<TId> other)
      {
        if (other == null)
          return false;
    
        if (ReferenceEquals(this, other))
          return true;
    
        if (!IsTransient(this) &&
            !IsTransient(other) &&
            Equals(Id, other.Id))
        {
          var otherType = other.GetUnproxiedType();
          var thisType = GetUnproxiedType();
          return thisType.IsAssignableFrom(otherType) ||
                 otherType.IsAssignableFrom(thisType);
        }
    
        return false;
      }
    
      public override int GetHashCode()
      {
        if (Equals(Id, default(TId)))
          return base.GetHashCode();
        return Id.GetHashCode();
      }
    
    }
  2. To the same file, add an additional Entity class as shown in the following code:

    public abstract class Entity : Entity<Guid>
    {
    }

How it works...

NHibernate relies on the Equals method to determine equality. The default behavior defined in System.Object uses reference equality for reference types, including classes. That is, x.Equals(y) is only true when x and y point to the same object instance. This default works well in most cases.

To support lazy loading, NHibernate uses proxy objects. As we learned in the previous recipe, these proxy objects are subclasses of the real entity class, with every member overridden to enable lazy loading.

This combination of proxy objects and the default Equals behavior can lead to subtle and unexpected bugs in your application. An application should not be aware of proxy objects, and therefore would expect that a proxy and a real instance representing the same entity would be equal. A Product instance with an ID of 8 should be equal to a different Product instance or Product proxy with an ID of 8. To handle this, we must override the default Equals behavior.

On our Entity base class, we override the Equals method to determine equality based on POID. In Equals(Object obj), we simply call Equals(Entity<TId> other), attempting to cast the object to Entity. If it can't be cast, null is passed instead.

If other is null, the objects are not equal. This serves two purposes. First, x.Equals(null) should always return false. Second, someEntity.Equals(notAnEntity) should also return false. Next, we compare references. Obviously, if two variables reference the same instance, they are equal. If ReferenceEquals(this, other) returns true, we return true.

Next, we compare the Ids to the default value to determine if the entities are transient. A transient object is an object that has not been persisted to the database. default(TId) returns whatever the default may be for TId. For Guids, the default is Guid.Empty. For strings and all other reference types, it's null. For numeric types, it's zero. If the Id property equals the default value, the entity is transient. If one or both entities are transient, we give up and return false.

If both entities are persisted, they both have POIDs. We can compare these POIDs to determine equality. If the POIDs don't match, we know for certain that the two entities are not equal. We return false.

Finally, we have one last check. We know that both entities are persistent, and they have the same Id. This doesn't quite prove that they're equal. It's perfectly legal for an ActorRole entity to have the same POID as a Product entity. Our last check is to compare the types. If one type is assignable to the other type, then we know for certain that the two are equal.

Suppose other is a proxy of Product representing a book entity, and this is an actual Book instance representing the same entity. this.Equals(other) should return true because they both represent the same entity. Unfortunately, other.GetType() will return the type ProductProxy12398712938 instead of the type Product. As typeof(ProductProxy12398712938).IsAssignableFrom(typeof(Book)) returns false, our Equals would fail on this case. However, we can use other.GetUnproxiedType() to reach down through the proxy layer and return the entity type. Because typeof(Product).IsAssignableFrom(typeof(Book)) returns true, our Equals implementation works.

Because we've overridden Equals, we also need to override GetHashCode to satisfy the requirements of the .NET Framework. Specifically, if x.Equals(y), then x.GetHashCode() and y.GetHashCode() should return the same value. The inverse is not necessarily true, however; x and y may share a hash code even when they're not equal. In our Entity base class, we simply use the hash code of Id, as this is the basis of our equality check.

There's more...

For more information on Equals and GetHashCode, refer to the MSDN documentation for these methods at http://msdn.microsoft.com/en-us/library/system.object.aspx.

See also

  • Mapping a class with XML

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Bidirectional one-to-many class relationships

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Handling versioning and concurrency


For any multiuser transactional system, you must decide between optimistic and pessimistic concurrency to handle concurrent updates and versioning issues. In this recipe, I'll show you how to properly set up versioning and optimistic concurrency with NHibernate.

Getting ready

Complete all the previous recipes including Setting up a base entity class.

How to do it...

  1. In the Entity base class, add a Version property, as shown in the following code:

    public abstract class Entity<TId>
    {
    
      public virtual TId Id { get; protected set; }
      protected virtual int Version { get; set; }
    
      public override bool Equals(object obj)
      {
        return Equals(obj as Entity<TId>);
      }
  2. In the Product mapping, add the version element as shown in the following code:

    <natural-id mutable="true">
      <property name="Name" not-null="true" />
    </natural-id>
    <version name="Version" />
    <property name="Description" />
    <property name="UnitPrice" not-null="true" />
  3. In the ActorRole mapping, add the version element shown here:

    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <version name="Version" />
    <property name="Actor" not-null="true" />
    <property name="Role" not-null="true" />

How it works...

Suppose you have a database application with two users. User #1 and user #2 both pull up the same data on their screen and begin making changes. User #1 submits her changes back to the database. A few moments later, user #2 submits his changes. Without any concurrency checking, user #2's changes will silently overwrite user #1's changes. There are two possible ways to prevent this: optimistic and pessimistic concurrency.

Optimistic concurrency is the process where data is checked for changes before any update is executed. In this scenario, user #1 and user #2 both begin their changes. User #1 submits her changes. When user #2 submits his changes, his update will fail because the current data (after user #1's changes) doesn't match the data that user #2 originally read from the database.

In the example shown here, we use the version field to track changes to an entity. Update statements takes the following form:

UPDATE Product
SET    Version = 2 /* @p0 */,
       Name = 'Junk' /* @p1 */,
       Description = 'Cool' /* @p2 */,
       UnitPrice = 100 /* @p3 */
WHERE  Id = '764de11e-1fd0-491e-8158-9db8015f9be5' /* @p4 */
       AND Version = 1 /* @p5 */

NHibernate checks that the version is the same value as when the entity was loaded from the database, and then increments the value. If the entity was already updated, the version field will not be 1, and no rows will be updated by this statement. NHibernate detects the zero rows affected and throws a StaleStateException, meaning the entity in memory is stale, or out of sync with the database.

There's more...

The alternative to optimistic concurrency is pessimistic locking. Pessimistic locking is the process where a user obtains an exclusive lock on the data while they are editing it. It takes the pessimistic view that, given the chance, user #2 will overwrite user #1's changes, so it's best not to let user #2 even look at the data. In this scenario, once user #1 pulls up the data, she has an exclusive lock. User #2 will not be able to read that data. His query will wait until user #1 drops the lock or the query times out. Inevitably, user #1 will take a phone call or step away for a cup of coffee while user #2 waits for access to the data. To implement this type of locking with NHibernate, your application must call session.Lock within a transaction.

Other methods of optimistic concurrency

In addition to integer version fields, NHibernate also allows you to use DateTime-based version fields. However, Micorosoft SQL Server has a datetime resolution of about three milliseconds. This may fail when two updates occur almost simultaneously. It's also possible to use SQL Server 2008's DateTime2 data type, which has a resolution of 100 nanoseconds, or even SQL Server's timestamp data type for the version field.

NHibernate allows you to use the more traditional form of optimistic concurrency through the mapping attribute optimistic-lock. A simple example would look like the following code:

<class name="Product" 
       dynamic-update="true" 
       optimistic-lock="dirty">

In this case, changing a Product name from Stuff to Junk would generate SQL as shown in the following code:

UPDATE Product
SET    Name = 'Junk' /* @p0 */
WHERE  Id = '741bd189-78b5-400c-97bd-9db80159ef79' /* @p1 */
       AND Name = 'Stuff' /* @p2 */

This ensures that the Name value hasn't been changed by another user because this user read the value. Another user may have changed other properties of this entity.

Another alternative is to set optimistic-lock to all. In this case, a Product update would generate SQL like this:

UPDATE Product
SET    Name = 'Junk' /* @p0 */
WHERE  Id = 'd3458d6e-fa28-4dcb-9130-9db8015cc5bb' /* @p1 */
       AND Name = 'Stuff' /* @p2 */
       AND Description = 'Cool' /* @p3 */
       AND UnitPrice = 100 /* @p4 */

As you might have guessed, in this case, we check the values of all properties.

When optimistic-lock is set to dirty, dynamic-update must be true. Dynamic update simply means that the update statement only updates dirty properties, or properties with changed values, instead of explicitly setting all properties.

See also

  • Mapping a class with XML

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Creating mappings fluently

  • Mapping with ConfORM

 

Creating mappings fluently


The Fluent NHibernate project brings strongly-typed C# fluent syntax mappings to NHibernate. In this recipe, I'll show you how to map our Eg.Core model using Fluent NHibernate.

Getting ready

Download the Fluent NHibernate binary from the Fluent NHibernate website at http://fluentnhibernate.org/downloads. Select a version that's compatible with the specific build of NHibernate you are using. The Fluent NHibernate download also contains the necessary assemblies for NHibernate. You may wish to use them instead.

Extract FluentNHibernate.dll from the downloaded ZIP file to the Lib folder.

Complete the previous Eg.Core model and mapping recipes.

How to do it...

  1. Create a new class library project named Eg.FluentMappings.

  2. Add a reference to FluentNHibernate.dll.

  3. Copy Entity.cs, Product.cs, Book.cs, Movie.cs, and ActorRole.cs from Eg.Core to the new Eg.FluentMappings.

  4. In the copied model, change the namespaces from Eg.Core to Eg.FluentMappings.

  5. In Entity.cs, change the Version property from protected to public.

  6. Add a new folder named Mappings.

  7. Create a new class named ProductMapping with the following code:

    using FluentNHibernate.Mapping;
    
    namespace Eg.FluentMappings.Mappings
    {
      public class ProductMapping : ClassMap<Product>
      {
    
        public ProductMapping()
        {
          Id(p => p.Id)
            .GeneratedBy.GuidComb();
          DiscriminateSubClassesOnColumn("ProductType");
          Version(p => p.Version);
          NaturalId()
            .Not.ReadOnly()
            .Property(p => p.Name);
          Map(p => p.Description);
          Map(p => p.UnitPrice)
            .Not.Nullable();
        }
    
      }
    }
  8. Create a new class named BookMapping with the following code:

    using FluentNHibernate.Mapping;
    
    namespace Eg.FluentMappings.Mappings
    {
      public class BookMapping : SubclassMap<Book>
      {
    
        public BookMapping()
        {
          Map(p => p.Author);
          Map(p => p.ISBN);
        }
    
      }
    }
  9. Create a new class named MovieMapping with the following code:

    using FluentNHibernate.Mapping;
    
    namespace Eg.FluentMappings.Mappings
    {
      public class MovieMapping : SubclassMap<Movie>
      {
    
        public MovieMapping()
        {
          Map(m => m.Director);
          HasMany(m => m.Actors)
            .KeyColumn("MovieId")
            .AsList(l => l.Column("ActorIndex"));
        }
    
      }
    }
  10. Create a new class named ActorRole with the following code:

    using FluentNHibernate.Mapping;
    
    namespace Eg.FluentMappings.Mappings
    {
      public class ActorRoleMapping : ClassMap<ActorRole>
      {
    
        public ActorRoleMapping()
        {
          Id(ar => ar.Id)
            .GeneratedBy.GuidComb();
          Version(ar => ar.Version);
          Map(ar => ar.Actor)
            .Not.Nullable();
          Map(ar => ar.Role)
            .Not.Nullable();
        }
    
      }
    }

How it works...

Fluent NHibernate provides two methods for mappings: Fluent mapping syntax and auto-mapping. In this recipe, we use the Fluent mapping syntax. Each entity class has a corresponding mapping class.

Because the mapping syntax requires class members to be accessible, we must change the Version property from protected to public. Fluent NHibernate also includes some tricks to work around this issue. They're explained fully in the wiki at http://wiki.fluentnhibernate.org/Fluent_mapping_private_properties.

Mappings for root classes are inherited from ClassMap, and subclasses in a class hierarchy inherit from SubclassMap. By default, Fluent NHibernate creates a table-per-subclass hierarchy. To use a table-per-class hierarchy instead, we specify DiscriminateSubClassesOnColumn in Product. Fluent NHibernate doesn't support table-per-concrete-class hierarchies.

When mapping the natural ID of Product, we specify .Not.ReadOnly(). This is the same as setting mutable="true" in the XML mapping.

Properties are mapped using the Map() method, which is equivalent to the property element in XML mappings.

One-to-many collections are mapped using the HasMany() method, followed by AsMap(), AsBag(), AsSet(), or AsList(). AsList uses the Column() method to specify a column name for the list index.

See also

  • Mapping a class with XML

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Bidirectional one-to-many class relationships

  • Handling versioning and concurrency

  • Creating mappings fluently

  • Mapping with ConfORM

 

Mapping with ConfORM


The ConfORM project brings convention-based mappings to NHibernate. In this recipe, I'll show you how to map your model using ConfORM conventions.

Getting ready

  1. Check out the ConfORM source code from Google Code at http://code.google.com/p/codeconform/source/checkout.

  2. Build the ConfORM project.

  3. Complete the previous Eg.Core model and mapping recipes.

How to do it...

  1. Create a new console project named Eg.ConfORMMappings.

  2. Add references to the Eg.Core model project, ConfORM.dll and ConfORM.Shop.dll.

  3. In Eg.Core.Entity, make the Version property public.

  4. In Program.cs, add the following using statements to the beginning of the file:

    using System;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using System.Xml.Serialization;
    using ConfOrm;
    using ConfOrm.NH;
    using ConfOrm.Patterns;
    using ConfOrm.Shop.CoolNaming;
    using Eg.Core;
    using NHibernate;
    using NHibernate.Cfg.MappingSchema;
  5. Add the following GetMapping function to the Program class:

    private static HbmMapping GetMapping()
    {
      var orm = new ObjectRelationalMapper();
      var mapper = new Mapper(orm, 
        new CoolPatternsAppliersHolder(orm));
    
      orm.TablePerClassHierarchy<Product>();
      orm.TablePerClass<ActorRole>();
    
      orm.Patterns.PoidStrategies.Add(
        new GuidOptimizedPoidPattern());
      orm.VersionProperty<Entity>(x => x.Version);
      orm.NaturalId<Product>(p => p.Name);
    
      orm.Cascade<Movie, ActorRole>(
        Cascade.All | Cascade.DeleteOrphans);
    
      mapper.AddPropertyPattern(mi => 
        mi.GetPropertyOrFieldType() == typeof(Decimal) && 
        mi.Name.Contains("Price"), 
        pm => pm.Type(NHibernateUtil.Currency));
    
      mapper.AddPropertyPattern(mi => 
        orm.IsRootEntity(mi.DeclaringType) && 
        !"Description".Equals(mi.Name), 
        pm => pm.NotNullable(true));
    
      mapper.Subclass<Movie>(cm => 
        cm.List(movie => movie.Actors, 
        colm => colm.Index(
          lim => lim.Column("ActorIndex")), m => { }));
    
    
      var domainClasses = typeof(Entity).Assembly.GetTypes()
        .Where(t => typeof(Entity).IsAssignableFrom(t));
    
      return mapper.CompileMappingFor(domainClasses);
    }
  6. Add the following WriteXmlMapping function:

    private static void WriteXmlMapping(HbmMapping hbmMapping)
    {
      var document = Serialize(hbmMapping);
      File.WriteAllText("WholeDomain.hbm.xml", document);
    }
  7. Add the following Serialize function:

    private static string Serialize(HbmMapping hbmElement)
    {
      var setting = new XmlWriterSettings { Indent = true };
      var serializer = new XmlSerializer(typeof(HbmMapping));
      using (var memStream = new MemoryStream(2048))
      using (var xmlWriter = XmlWriter.Create(memStream, setting))
      {
        serializer.Serialize(xmlWriter, hbmElement);
        memStream.Flush();
        memStream.Position = 0;
        using (var sr = new StreamReader(memStream))
        {
          return sr.ReadToEnd();
        }
      }
    }
  8. In the static void Main method, add the following line:

    WriteXmlMapping(GetMapping());
  9. Build and run your application.

  10. Browse to the application's bin\Debug folder and examine the WholeDomain.hbm.xml file. You should find the following familiar mapping:

    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="Eg.Core" assembly="Eg.Core" xmlns="urn:nhibernate-mapping-2.2">
      <class name="Product">
        <id name="Id" type="Guid">
          <generator class="guid.comb" />
        </id>
        <discriminator />
        <natural-id>
          <property name="Name" not-null="true" />
        </natural-id>
        <version name="Version" />
        <property name="Description" />
        <property name="UnitPrice" type="Currency" 
          not-null="true" />
      </class>
      <class name="ActorRole">
        <id name="Id" type="Guid">
          <generator class="guid.comb" />
        </id>
        <version name="Version" />
        <property name="Actor" not-null="true" />
        <property name="Role" not-null="true" />
      </class>
      <subclass name="Book" extends="Product">
        <property name="ISBN" />
        <property name="Author" />
      </subclass>
      <subclass name="Movie" extends="Product">
        <property name="Director" />
        <list name="Actors" cascade="all,delete-orphan">
          <key column="MovieId" />
          <list-index column="ActorIndex" />
          <one-to-many class="ActorRole" />
        </list>
      </subclass>
    </hibernate-mapping>

How it works...

With a standard NHibernate application, NHibernate takes each XML mapping and deserializes it into an HbmMapping object, then adds the HbmMapping object to the NHibernate configuration, as shown in the next diagram:

With ConfORM, we skip this deserialization step. The ConfORM mapper outputs an HbmMapping object built from our conventions, ready to be added to the configuration.

ConfORM uses conventions and patterns to build a mapping directly from the model. In addition to ConfORM's default patterns, we use a few extra conventions in our model.

  1. We begin by specifying our Product class hierarchy, which includes Books and Movies. We also add our ActorRole entity class individually.

  2. We use the GuidOptimizedPoidPattern to find all Guid properties named Id, and map them as POIDs with the guid.comb generator.

  3. References from Movie to ActorRole, such as our Actors collection, should use cascade="all-delete-orphan". We set this up with the following bit of code:

    orm.Cascade<Movie, ActorRole>(
      Cascade.All | Cascade.DeleteOrphans);
  4. Next, we configure a few conventions. All decimal properties with the word Price in the property name should be mapped as type="currency". We use the following code:

    mapper.AddPropertyPattern(mi => 
      mi.GetPropertyOrFieldType() == typeof(Decimal) && 
      mi.Name.Contains("Price"), 
      pm => pm.Type(NHibernateUtil.Currency));
  5. All properties of root entities, except those named Description, are mapped with not-null="true". Remember, we're using the table-per-class hierarchy strategy, so our subclasses shouldn't have not-null properties. The code we use is as follows:

    mapper.AddPropertyPattern(mi => 
      orm.IsRootEntity(mi.DeclaringType) && 
      !"Description".Equals(mi.Name), 
      pm => pm.NotNullable(true));
  6. Finally, we map our Actors list in Movies, setting the list index column name to ActorIndex. We use the following code:

    mapper.Subclass<Movie>(cm => 
      cm.List(movie => movie.Actors, 
      colm => colm.Index(
        lim => lim.Column("ActorIndex")), m => { }));
  7. The last step in building our HbmMapping object is to call CompileMappingFor, passing in every Entity type, as shown in the following code:

    var domainClasses = typeof(Entity).Assembly.GetTypes()
      .Where(t => typeof(Entity).IsAssignableFrom(t));
    
    return mapper.CompileMappingFor(domainClasses);
  8. The resulting mapping object is equivalent to XML mapping contained in WholeDomain.hbm.xml.

See also

  • Mapping a class with XML

  • Creating class hierarchy mappings

  • Mapping a one-to-many relationship

  • Setting up a base entity class

  • Bidirectional one-to-many class relationships

  • Handling versioning and concurrency

  • Creating mappings fluently

 

Bidirectional one-to-many class relationships


In some cases, it's useful to have a bidirectional relationship between entities. In this recipe, I'll show you how to set up a bidirectional one-to-many relationship between two entity classes.

How to do it...

  1. Create an empty class library project named ManualRelationships.

  2. Add a reference to Iesi.Collections.dll in the Lib folder.

  3. Add the following Order class:

    public class Order
    {
    
      public virtual Guid Id { get; protected set; }
    
      public Order()
      {
        _items = new HashedSet<OrderItem>();
      }
    
      private ISet<OrderItem> _items;
      public virtual IEnumerable<OrderItem> Items
      {
        get
        {
          return _items;
        }
      }
    
      public virtual bool AddItem(OrderItem newItem)
      {
        if (newItem != null && _items.Add(newItem))
        {
          newItem.SetOrder(this);
          return true;
        }
        return false;
      }
    
      public virtual bool RemoveItem(
        OrderItem itemToRemove)
      {
        if (itemToRemove != null && 
          _items.Remove(itemToRemove))
        {
          itemToRemove.SetOrder(null);
          return true;
        }
        return false;
      }
    
    }
  4. Add the following mapping as an embedded resource named Order.hbm.xml:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="ManualRelationships"
        namespace="ManualRelationships">
      <class name="Order" table="`Order`">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <set name="Items" 
             cascade="all-delete-orphan" 
             inverse="true" 
             access="field.camelcase-underscore">
          <key column="OrderId" />
          <one-to-many class="OrderItem"/>
        </set>
      </class>
    </hibernate-mapping>
  5. Add the following OrderItem class:

    public class OrderItem
    {
    
      public virtual Guid Id { get; protected set; }
    
      public virtual Order Order { get; protected set; }
    
      public virtual void SetOrder(Order newOrder)
      {
        var prevOrder = Order;
    
        if (newOrder == prevOrder)
          return;
    
        Order = newOrder;
    
        if (prevOrder != null)
          prevOrder.RemoveItem(this);
    
        if (newOrder != null)
          newOrder.AddItem(this);
    
      }
    
    }
  6. Add the following mapping as an embedded resource named OrderItem.hbm.xml:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="ManualRelationships"
        namespace="ManualRelationships">
      <class name="OrderItem">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <many-to-one name="Order" column="OrderId" />
      </class>
    </hibernate-mapping>

How it works...

Object relational mappers (ORM) are designed to overcome the impedance mismatch between the object model in the application and the relational model in the database. This mismatch is especially evident when representing a bidirectional one-to-many relationship between entities. In the relational model, this bidirectional relationship is represented by a single foreign key. In the object model, the parent entity has a collection of children, and each child has a reference to its parent.

To work around this mismatch, NHibernate ignores one side of the bidirectional relationship. The foreign key in the database is populated based on either the OrderItems reference to the Order or the Orders collection of OrderItems, but not both. We determine which end of the relationship controls the foreign key using the inverse attribute on the collection. By default, the Order controls the foreign key. Saving a new Order with one OrderItem will result in the following three SQL statements:

INSERT INTO "Order" (Id) VALUES (@p0)
INSERT INTO OrderItem (Id) VALUES (@p0)
UPDATE OrderItem SET OrderId = @p0 WHERE Id = @p1

When we specify inverse="true", the OrderItem controls the foreign key. This is preferable because it eliminates the extra UPDATE statement, resulting in the following two SQL statements:

INSERT INTO "Order" (Id) VALUES (@p0)
INSERT INTO OrderItem (OrderId, Id) VALUES (@p0, @p1)

We are responsible for keeping both sides of our two-way relationship in sync. In a normal class, we would add code in the property setter or the collection's add or remove methods to update the other end of the relationship automatically. NHibernate, however, throws exceptions when an object is manipulated while NHibernate is initializing it.

For this reason, it's suggested that we prevent direct manipulation of either end of the relationship, and instead use methods specifically written for this purpose, as we've done here with AddItem, RemoveItem, and SetOrder. Notice that we've mapped a set, which implies that order is not significant, and that duplicates are not allowed.

There's more...

Notice the use of backticks in our table name from the Order mapping as follows:

<class name="Order" table="`Order`">

In Microsoft SQL Server, Order is a keyword. If we want to use it as an identifier, a table name in this case, NHibernate will need to put quotes around it. The backticks tell NHibernate to surround the identifier with whatever character may be appropriate for the database you're using.

 

Mappings enumerations


An improperly mapped enumeration can lead to unnecessary updates. In this recipe, I'll show you how to map an enumeration property to a string field.

How to do it...

  1. Create a new class library project named MappingEnums.

  2. Add the following AccountTypes enumeration:

    public enum AccountTypes
    {
      Consumer,
      Business,
      Corporate,
      NonProfit
    }
  3. Add the following Account class:

    public class Account
    {	
      public virtual Guid Id { get; set; }
      public virtual AccountTypes AcctType { get; set; }
      public virtual string Number { get; set; }
      public virtual string Name { get; set; }
    }
  4. Add an NHibernate mapping document with the following class mapping:

    <class name="Account">
      <id name="Id">
        <generator class="guid.comb" />
      </id>
      <natural-id>
        <property name="Number" not-null="true"  />
      </natural-id>
      <property name="Name" not-null="true" />
      <property name="AcctType" not-null="true" />
    </class>
  5. On the property element for AcctType, add a type attribute with the following value:

    NHibernate.Type.EnumStringType`1[[MappingEnums.AccountTypes, MappingEnums]], NHibernate
  6. Set your mapping as an embedded resource.

How it works...

By default, NHibernate will map an enumeration to a numeric field based on the enumeration's underlying type, typically an int. For example, if we set AcctType to AccountTypes.Corporate, the AcctType database field would hold the integer 2. This has one significant drawback. An integer value by itself doesn't describe the business meaning of the data.

One solution is to create a lookup table containing each enumeration value alongside a description, but this must be maintained in perfect sync with the application code because otherwise it can lead to serious versioning issues. Simply rearranging the order of the enumeration in code from one release to the next can have disastrous effects.

Another solution, the one shown here, is to store the name of the enumeration value in a string field. For example, if we set AcctType to AccountTypes.Corporate, the AcctType database field would hold the string value Corporate.

By specifying a type attribute for AcctType, we tell NHibernate to use a custom class for conversion between .NET types and the database. NHibernate includes EnumStringType<T> to override the conversion of enumeration values to database values so that the string name is stored, not the numeric value.

The type value NHibernate.Type.EnumStringType`1[[MappingEnums.AccountTypes, MappingEnums]], NHibernate is the assembly qualified name for NHibernate.Type.EnumStringType<AccountType>.

 

Creating class components


There are cases where a set of properties are used repeatedly. These properties may even have their own business logic, but they don't represent an entity in your application. They are value objects. In this recipe, I'll show you how we can separate these properties and business logic into a component class without creating a separate entity.

How to do it...

  1. Create a new class library project named ComponentExamples.

  2. Add an Address class with the following properties:

    public virtual string Lines { get; set; }
    public virtual string City { get; set; }
    public virtual string State { get; set; }
    public virtual string ZipCode { get; set; }
  3. Add a customer class with the following properties:

    public virtual string Name { get; set; }
    public virtual Address BillingAddress { get; set; }
    public virtual Address ShippingAddress { get; set; }
  4. Add the following mapping document:

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
        assembly="ComponentExamples"
        namespace="ComponentExamples">
      <class name="Customer">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="Name" not-null="true" />
        <component name="BillingAddress" class="Address">
          <property name="Lines" not-null="true" />
          <property name="City" not-null="true" />
          <property name="State" not-null="true" />
          <property name ="ZipCode" not-null="true" />
        </component>
        <component name="ShippingAddress" class="Address">
          <property name="Lines" not-null="true" 
                    column="ShippingLines" />
          <property name="City" not-null="true" 
                    column="ShippingCity" />
          <property name="State" not-null="true" 
                    column="ShippingState" />
          <property name ="ZipCode" not-null="true" 
                    column="ShippingZipCode" />
        </component>
      </class>
    </hibernate-mapping>

How it works...

In this recipe, we can use the Address component class throughout our model without the overhead of maintaining a separate entity. We've used it in our Customer class for both billing and shipping address. The resulting database table will appear as shown in the next screenshot:

Our model looks like this:

We get all the reuse benefits without the database work. The Address fields are included in every query for Customer, and are automatically loaded.

About the Author
  • Jason Dentler

    Jason Dentler grew up in the small Texas town of Mission Valley. He started tinkering with computers as a kid in the late 1980s, and all these years later, he hasn't stopped. He's worked in a few different industries. Currently, he builds really awesome software in higher education. He's an Eagle Scout and a graduate of the University of Houston – Victoria.

    Browse publications by this author
NHibernate 3.0 Cookbook
Unlock this book and the full library FREE for 7 days
Start now