Chapter 2. Models and Mappings

In this chapter, we will cover the following topics:

  • Mapping a class with XML
  • Creating class hierarchy mappings
  • Creating class components
  • Mapping a one-to-many relationship
  • Mapping a many-to-many relationship
  • Mapping collections of elements and components
  • Setting up a base entity class
  • Handling versioning and concurrency
  • Mapping by code
  • Mapping by convention
  • Creating mappings fluently
  • Bidirectional one-to-many class relationships
  • Mappings enumerations
  • Immutable entities
  • Mapping relations to non-primary keys
  • Using lazy properties
  • Mapping joins
  • Using calculated properties
  • Using serializable values
  • Dynamic components
  • Mapping <subselect>

Mapping a class with XML

The suggested first step in any new NHibernate application is mapping the model. The mapping describes how objects O should be retrieved and stored as relational data R in the database. In the simplest scenarios, this is merely a straightforward mapping between classes in the code and tables in the database. Still, the structure of the code or of the database will usually require careful mapping in order to get correct behavior and optimal performance.

Note

Most of the recipes in this chapter utilize a custom library called NH4CookbookHelpers, which makes it possible to visualize the results and behaviors of the mappings described. For convenience, we use NuGet to reference this library, but feel free to download the source code from https://github.com/gliljas/NH4CookbookHelpers if you want to modify its functionality or just see how it works.

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. Complete the steps from the Installing NHibernate recipe.
  2. Add a Windows forms application project to your solution called MappingRecipes and set it as the startup project.
  3. Install NH4CookbookHelpers to the MappingRecipes project using the NuGet package manager console:
    Install-Package NH4CookbookHelpers -Project MappingRecipes
    
  4. Add a reference to for Eg.Core (from Installing NHibernate) in the MappingRecipes project.
  5. Remove the class Form1.cs from the project.
  6. Add using NH4CookbookHelpers; to the top of Program.cs.
  7. Edit Program.cs so that the last line in Main reads, Application.Run(new WindowsFormsRunner());:
    Getting ready

The original technique for NHibernate mapping is the use of XML files.

In this first example, we'll show you how to map a simple Product class.

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.
  4. Next, let's create an NHibernate mapping for our Product class by following these steps:
    1. In the Solution Explorer window, right-click on the Eg.Core 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:
      <?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>
  5. Finally, we will make use of the mapping by reading from the embedded resources and adding the parsed result to NHibernate's configuration.
  6. Add a new folder MappingWithXml to the MappingRecipes project.
  7. Add a new class Recipe to the newly created folder:
    using Eg.Core;
    using NH4CookbookHelpers;
    using NHibernate;
    using NHibernate.Cfg;
    
    namespace MappingRecipes.MappingWithXml
    {
        public class Recipe : BaseMappingRecipe
        {
            protected override void Configure(Configuration cfg)
            {
                cfg.AddAssembly(typeof(Product).Assembly);
            }
    
            public override void AddInitialData(ISession session)
            {
                session.Save(new Product
                {
                    Name = "Car",
                    Description = "A nice red car",
                    UnitPrice = 300
                });
            }
        }
    }
  8. Run the MappingRecipes application and start the MappingWithXml recipe.
  9. Investigate the query log and the created tables.

How it works…

In this recipe, we begin by creating our own model. The model is a 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.

Note 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. As a rule, all NHibernate mapping files end with a .hbm.xml extension and have a build action of Embedded Resource. NHibernate can search 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 assembly attribute tells NHibernate, by default, which assembly 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 if and how NHibernate will generate POIDs. In this case, we have 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 have to be maintained. The default table name matches the class name, and each column name matches the corresponding property by default and 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:

How it works…

In this recipe, we add the mappings to NHibernate's configuration in the overridden recipe method Configure. We simply call cfg.AddAssembly(typeof(Product).Assembly), which instructs NHibernate to scan all the embedded resources in the specified assembly. In a normal application, this call will be added somewhere close to the other NHibernate configuration calls, as outlined in the Chapter 1, The Configuration and Schema.

There's more…

There are two main approaches to begin developing an NHibernate application:

  • With the code-first approach, the path taken in this book, we create our entity classes, specify the needed mappings, and finally generate our database tables based on this setup. We treat the database as a storage and querying system only and structure the tables and columns accordingly.
  • 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 modeling 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 a 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's a value that identifies data in a database table in a unique manner. 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 a 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.

Specifying a natural-id is optional. The difference it provides are some subtle improvements in caching and if NHibernate is used to create the database schema, the natural-id will be given a unique database index.

ID generator selection

NHibernate offers many options for generating POIDs. Some are better than others and generally fall under the following 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, thus allowing it to delay writing until the business transaction is complete and reducing round trips to the database. The following POID generators fit in this category:
    • hilo: This generates an integer using the Hi/Lo algorithm, where an entire range of integers is reserved and used as needed. Once they have 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: This generates a GUID by calling System.Guid.NewGuid(). All the GUID-based generators are safe for use in a shared database environment.
    • guid.comb: This 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: This gets a GUID from the database. Each generation requires a round trip to the database.
    • uuid.hex: This generates a GUID and stores it as a human readable string of 32 hex digits, with or without dashes.
    • uuid.string: This generates a GUID, converts each of the GUID instance 16 bytes to the binary equivalent character, and stores the resulting 16 characters as a string. Tshis is not readable by a human.
    • counter: This is also known as vm. It is a simple incrementing integer. It's initialized from the system clock and counts up; it is not appropriate for shared database scenarios.
    • increment: It is a simple incrementing integer, initialized by fetching the maximum primary key value from the database at start-up. It's not appropriate for shared database scenarios.
    • sequence: This etches 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 performs better.
    • seqhilo: This combines the Hi/Lo algorithm and sequences to provide better performance over the sequence generator.
    • foreign: This 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 affects 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 be used only 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 a 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
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.110.183