XML mappings for the Employee class

Learning mapping through documentation may not be the best experience for beginners. What can work better is using one of the classes from our domain model as an example and guiding you through the mapping exercise. That is exactly what we are going to do in this section. We will begin our journey with writing XML mapping for the Employee class.

The Employee class has some attributes of primitive type as well as collections and associations to other classes. To keep things simple in the beginning, we will first look at mappings of the primitive attributes of the Employee class and not the associations to the other classes. The objective is to understand how to declare simple mappings before we start handling complex scenarios.

Before we start mapping, I would like to talk about making the development environment ready for XML mapping and NHibernate in general. What I mean by that is ensuring that we have projects in place, third-party libraries installed, and any Visual Studio configuration done. We are going to use unit tests to verify mappings. Any VS configuration needed to write and run unit tests will also be discussed.

Getting the development environment ready

In our Visual Studio solution, we already have a project named Domain that holds all our domain entities. Let's create another project of type Class Library named Persistence. We will add our XML mappings in this project. Follow the steps listed next to set yourself up for your XML mappings tour:

  1. In the Persistence project, add reference to the Domain project.
  2. Add the NuGet package NHibernate to the Persistence project.
  3. Add a folder named Mappings to the newly created Persistence project.
  4. Add another folder named Xml under the newly added Mappings folder. We are going to add our XML mappings into this folder.

    Note

    The Persistence project has referenced the Domain project. So Persistence has knowledge of Domain but Domain does not have any knowledge of Persistence. This is inspired from Onion Architecture principle. As we will learn in Chapter 8, Using NHibernate in a Real-world Application, Domain usually holds and should hold all the business logic. This business logic should not be dependent on a particular database or a particular software library being used. Presentation layers, data access layers are all specific details which are needed to make business logic work but business logic should not depend on these. Business logic should plainly be dependent on your domain model, which in our case is defined inside the Domain project. Uncle Bob Martin calls this The Clean Architecture which he has explained in his blog at http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html.

Every mapping must be named such that the name ends with .hbm.xml. Lot of developers follow a convention to name the files as <Entity name>.hbm.xml for simplicity and readability. For instance, XML mapping file for the Employee class would be named Employee.hbm.xml. But this is not a prerequisite and you are free to name your mapping files whatever you like, as long as they end with .hbm.xml.

Getting IntelliSense to work with the XML mapping files

One of the greatest powers of Visual Studio comes from its IntelliSense feature. As a long time user of Visual Studio (and ReSharper), I find it uncomfortable when IntelliSense does not work for some non-standard files. Fortunately for XML mappings, NH's authors have provided XSD files that will enable IntelliSense. Follow the steps listed next to enable IntelliSense for your XML mapping files. For these instructions, I am assuming that a file named Employee.hbm.xml is added to your project.

  1. Open Employee.hbm.xml by double clicking on the file in Solution Explorer.
  2. Once the file is open, right-click anywhere within the file.
  3. On the menu that appears, choose the Properties option.
  4. This will open properties dialog for Xml Document, as shown in the following image. Click on the Schemas property and a button with ellipsis becomes visible:
    Getting IntelliSense to work with the XML mapping files
  5. Click on the ellipsis button, which will bring up the XML Schemas dialog, as shown in the next screenshot:
    Getting IntelliSense to work with the XML mapping files
  6. Click on the Add… button which will open a browse file dialog.
  7. Browse to where your NuGet packages are downloaded. This is usually inside a folder named packages at solution folder level. Locate the folder of NHibernate package inside this folder, you would find two XSD files named nhibernate-configuration.xsd and nhibernate-mapping.xsd. Choose both these files.
  8. Click OK on the XML Schemas dialog.
  9. Congratulations, you have successfully enabled IntelliSense for mapping files.

With that, your development environment should be ready for working with XML mappings. So let's start with mapping the Employee class.

Unit tests to verify the Employee mappings

To verify that mappings are correct and they map to the database table as per our expectations, we will create an instance of the Employee class and save it to an in-memory database. We will then retrieve the instance we just saved and confirm that all the attribute values we had saved are retrieved from the database. Add a new class to project Tests.Unit and call it InMemoryDatabaseForXmlMappings. Use the following code snipped to define the class:

using System;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Tool.hbm2ddl;
using Environment = NHibernate.Cfg.Environment;

namespace Tests.Unit
{
  public class InMemoryDatabaseForXmlMappings : IDisposable
  {
    protected Configuration config;
    protected ISessionFactory sessionFactory;

    public InMemoryDatabaseForXmlMappings()
    {
      config = new Configuration()
      .SetProperty(Environment.ReleaseConnections, "on_close")
      .SetProperty(Environment.Dialect, typeof (SQLiteDialect).AssemblyQualifiedName)
      .SetProperty(Environment.ConnectionDriver, typeof (SQLite20Driver).AssemblyQualifiedName)
      .SetProperty(Environment.ConnectionString, "data source=:memory:")
      .AddFile("Mappings/Xml/Employee.hbm.xml");

      sessionFactory = config.BuildSessionFactory();

      Session = sessionFactory.OpenSession();

      new SchemaExport(config).Execute(true, true, false, Session.Connection, Console.Out);
    }

    public ISession Session { get; set; }

    public void Dispose()
    {
      Session.Dispose();
      SessionFactory.Dispose();
    }
  }
}

As you can see, there is a lot going on here. Let me give you a brief explanation of what the preceding code is doing. In the constructor of the class, we have created an instance of NH Configuration object and have set different configuration values. These properties tell NHibernate what kind of database we are going to use and where NHibernate can find this database. An important point to note is that we are using an in-memory version of SQLite database. We then add the Employee.hbm.xml file to configuration to let NHibernate know that this file contains the mappings for the Employee entity. Note that adding the mapping XML file directly to configuration file is one of the ways of configuring mappings. NHibernate supports several different ways which we will cover in more detail in the next chapter. We then create an instance of session object. If you remember from Chapter 1, Introduction to NHibernate, session object is what lets us connect to the database. The last line which uses the SchemaExport class, will use the mappings supplied to configuration and build database schema into the in-memory database. This database schema is built based on the mappings we would declare. I am sure this is information overload for you at this time but feel free to ignore all the details and just remember that this class lets us use an in-memory database for our tests. In the next chapter, we are going to take a look at NHibernate configuration in detail.

With the configuration sorted out, let's look at our first test. Since we are only going to map primitive properties on the Employee class, our first test is going to be about verifying that primitive properties of the Employee class are saved correctly in the database. Add a new folder named Mappings to the project Test.Unit. In this folder, add a new class named EmployeeMappingsTests. Add a method named MapsPrimitiveProperties to this class, as shown next:

using System;
using Domain;
using NHibernate;
using NUnit.Framework;

namespace Tests.Unit.Mappings.Xml
{
  [TestFixture]
  public class EmployeeMappingsTests
  {
    private InMemoryDatabaseForXmlMappings database;
    private ISession session;

    [TestFixtureSetUp]
    public void Setup()
    {
      database = new InMemoryDatabaseForXmlMappings();
      session = database.Session;
    }

    [Test]
    public void MapsPrimitiveProperties()
    {
      object id = 0;
      using (var transaction = session.BeginTransaction())
      {
        id = session.Save(new Employee
          {
            EmployeeNumber = "5987123",
            Firstname = "Hillary",
            Lastname = "Gamble",
            EmailAddress = "[email protected]",
            DateOfBirth = new DateTime(1980, 4, 23),
            DateOfJoining = new DateTime(2010, 7, 12),
            IsAdmin = true,
            Password = "Password"
          });
          transaction.Commit();
      }

      session.Clear();

      using (var transaction = session.BeginTransaction())
      {
        var employee = session.Get<Employee>(id);
        Assert.That(employee.EmployeeNumber, Is.EqualTo("5987123"));
        Assert.That(employee.Firstname, Is.EqualTo("Hillary"));
        Assert.That(employee.Lastname, Is.EqualTo("Gamble"));
        Assert.That(employee.EmailAddress, Is.EqualTo("[email protected]"));
        Assert.That(employee.DateOfBirth.Year, Is.EqualTo(1980));
        Assert.That(employee.DateOfBirth.Month, Is.EqualTo(4));
        Assert.That(employee.DateOfBirth.Day, Is.EqualTo(23));
        Assert.That(employee.DateOfJoining.Year, Is.EqualTo(2010));
        Assert.That(employee.DateOfJoining.Month, Is.EqualTo(7));
        Assert.That(employee.DateOfJoining.Day, Is.EqualTo(12));
        Assert.That(employee.IsAdmin, Is.True);
        Assert.That(employee.Password, Is.EqualTo("Password"));
        transaction.Commit();
      }
    }
  }
}

Note

If you noticed, we have added a [TestFixtureSetup] attribute to the Setup method. This is a special attribute that tells NUnit that this method should be called once before the tests in this class are executed. Similarly, there is another attribute named [TextFixtureTearDown]. Method decorated with this attribute is called by NUnit after all the tests in that fixture are run.

We have a method named Setup in which we create an instance of InMemoryDatabaseForXmlMappings and get hold of the session object. We use this session to store the instance of the Employee object that we have created. Ignore the call to method BeginTransaction for now. After saving the employee instance, session is cleared in order to remove any traces of employee instance from previous database operation. We then use the same session object to load the employee record that we just saved and verify that properties on the loaded instance have same values as we had set previously.

Note

I tend not to add more than 4-5 unit tests in one class. So the unit tests that we refer to in this chapter may not all be in the same class as this one. But there is one thing in common between all the unit test classes. The Setup method and its code are exactly same in every unit test. You can move this into a common base class if needed. Also note that, henceforth, I will only show you unit test in question but no Setup method or any other details. This is to keep the text of the chapter brief.

If you run this test now, it would fail for obvious reasons. We have not added any mappings yet. Let's go ahead and start writing mappings for the properties we are testing above.

The mappings

In the previous section, we have seen that XML mappings are written in a file with an extension .hbm.xml. In case of the Employee class, this would be Employee.hbm.xml. Following is how primitive properties of the Employee class would be mapped:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Employee">
    <id name="Id" generator="hilo" />
    <property name="EmployeeNumber" />
    <property name="Firstname" />
    <property name="Lastname" />
    <property name="EmailAddress" />
    <property name="DateOfBirth" />
    <property name="DateOfJoining" />
    <property name="IsAdmin" />
    <property name="Password" />
  </class>
</hibernate-mapping>

Let's try to understand the purpose of different XML elements in the preceding code:

  • hibernate-mapping: This element is root element of the mapping file and holds all the mapping information in its child elements. Other than its standard XML namespace attribute, this element has two other important attributes. First one is assembly which is used to declare name of the assembly in which the class Employee is declared. Second attribute is namespace which is used to declare the complete namespace the class is declared in. In our case both happen to be Domain.
  • class: This element is used to declare the name of the class for which we are writing this mapping. Attribute name is clearly used to declare the name of the class.
  • id: This element is used to specify details of the identifier property of this entity. Attribute name specifies the property on class that holds the identifier value. Attribute generator tells how NHibernate should generate the identifier value. We had briefly discussed this in the Mapping the prerequisites section at the beginning of this chapter. The value passed in this attribute is short name of one of the several identifier generation algorithms that come bundled with NHibernate.
  • property: This element is used to map properties on class that are of primitive type. Attribute name specifies which property of class is being mapped.

If we run our test now, it should pass happily.

What we have seen above is the absolute minimum that NHibernate needs declared in order to make mappings work. But there are more attributes available for each of the elements we discussed previously. These attributes let us control mappings to a great level of detail. NHibernate assumes safe defaults when these attributes are not declared. Let's take a look at some of the important and most useful optional attributes of each of these elements.

Hibernate-mapping

A detailed hibernate-mapping declaration for the preceding example would look as the following:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
  schema="EB"
  default-cascade="delete-all-orphan"
  assembly="Domain"
  namespace="Domain"
  default-access="property"
  default-lazy="true">

Let's look at what these additional attributes give us:

  • schema: Often times, we use database schemas to divide our tables into logical groups. You must tell NHibernate if that is the case. Usually, you tell NHibernate of this at class level but if you have kept all your tables in the same schema then you will not want to repeat this information at every class level. NHibernate lets you declare this globally at hibernate-mapping level. If not declared, NHibernate assumes a default value of dbo in case of MS SQL Server.
  • default-cascade: This lets you specify the default cascade style for your entire application. If not specified, none is assumed.

    Note

    Cascade defines how saves, deletes, and updates to tables, which are related with each other via foreign keys, work. When a record in the parent table is deleted then you can configure to have record in the child tables to be either deleted automatically or left there as orphan and so on. Similarly, when a parent entity is saved then contained associated entities are also saved automatically by NHibernate if cascade is set to do so.

  • default-access: We have mapped properties on the Employee class in the mappings we just saw. But NHibernate is not limited to mapping properties only. It can map fields as well. With this attribute, you can tell NHibernate that you want to map fields by default and not properties. Again, if not specified, NHibernate assumes property.
  • default-lazy: This attribute turns on lazy loading for the entire application if set to true. We have briefly talked about lazy loading at the beginning of this chapter. We will cover it in detail in one of the upcoming chapters. NHibernate assumes a default of true for this setting.

    Note

    The global scope of these attributes will come in effect only when you keep all mappings in one XML file under one hibernate-mapping node. I personally do not like this style and tend to keep mappings of related classes in one XML file, in which case, I would need to add these attributes once per file.

Class

A detailed mapping declaration for the class element from example would look as follows:

<class name="Employee"
  table="employees"
  mutable="true"
  schema="EB"
  lazy="true">

Let's examine the additional attributes to understand what additional features they offer:

  • table: This attribute declares the name of the table the class should be mapped to. If not declared, NHibernate uses a concept called naming strategy to determine the name of the table to which entity is being mapped. A naming strategy is implemented by realizing the interface NHibernate.Cfg.INamingStrategy. There are two implementations of this, available out-of-the-box. First one, DefaultNamingStrategy uses the name of the entity as is for the table name. As the name suggests, this is the default naming strategy that NHibernate will use if the table name is not specified explicitly during mapping. Second implementation is ImprovedNamingStrategy which uses underscore separate Pascal case segments from the name of the entity to form the name of the table. For example, an entity named SeasonTicketLoan would be mapped to a database table named season_ticket_loan.
  • mutable: This attribute specifies whether instances of this class should be mutable or not. By default, NHibernate assumes a value of true but you can make the instances immutable by setting this value to false.
  • schema: This is the same as the schema attribute we discussed previously but the scope is limited to the class being mapped.
  • lazy: This is similar to the default-lazy attribute discussed previously with scope limited to the class being mapped.

Property

Property mapping with important optional attributes specified would look as follows:

<property
  name="EmployeeNumber"
  column="employment_number"
  type="System.String"
  length="10"
  not-null="true"
  unique="true"
  lazy="false"
  mutable="true"/>

Again, let's examine the meaning of the additional attributes:

  • column: It is used to specify the name of the mapped database table column. If not defined, NHibernate uses the naming strategy to determine the column name. This is the same naming strategy that we touched upon during discussion of the table attribute on class mapping previously.
  • type: Type of the mapped database column. NHibernate has a carefully designed system that uses reflection to check the type of property being mapped and determine the most appropriate database type for the mapped table column. If for any reason you are not happy with the type that NHibernate decides for a particular column, you can override that and specify your own type using this attribute. NHibernate accepts some primitive .NET types as well as offers some more types that can be used here. In the appendix, you can find table that describes which .NET/NHibernate types is mapped to which database type.
  • length: NHibernate can generate database scripts based on your mappings. We will look at this feature of NHibernate in one of the chapters. NHibernate uses information you specify in the mappings to come up with a database schema that accurately maps to your domain model. To that end, length attribute can be used on properties that map to database types supporting length for example, char. This attribute is optional and takes a default of 255 if not specified.
  • not-null: This attribute is used in situation similar to the previous one and declare the mapped table column to be NOT NULL.
  • unique: This is one more attribute in the same category. This attribute marks the mapped table column as UNIQUE.
  • lazy: This is similar to the lazy attribute on class with the difference that scope is limited to the property being mapped. This is really useful only for CLOB and BLOB type of properties.
  • mutable: This is same as the mutable attribute on the class element with the only difference being the scope is limited to current property.

Note

I have intentionally skipped some of the important optional attributes here. These attributes control how NHibernate stores/retrieves data for the property. I intend to talk about these in detail in Chapter 5, Let's Store Some Data into the Database, where we will look at storing and retrieving data.

So far we have just scratched the surface of NHibernate mappings. There is so much more to it that a chapter of a book is not enough to explain this topic in detail. In this section, I wanted to give you a glimpse of NHibernate mappings. Now that you have got some idea of what NHibernate mappings are, how to declare them, and how they work, I would like to present some details around some advanced mapping scenarios, mainly around mapping associations, component, and inheritance mapping strategies.

..................Content has been hidden....................

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