Preparing The System To Use NHibernate In An Application

in #utopian-io8 years ago (edited)

What Will I Learn?

How To Prepare The System To Use NHibernate In An Application

Requirements

  • NHibernate
  • Fluent NHibernate
  • SVN client

Difficulty

  • Intermediate

Tutorial Contents

  1. Introduction
  2. Object Model first approach
  3. The domain model
  4. Mapping the domain model
  5. Preparing my system to use NHibernate
  6. Install an SVN client application
  7. Download Fluent NHibernate
  8. Setup a new solution
  9. Implementing and mapping the first object of the domain model
  10. Test the Mapping
  11. The Test Fixture Base Class

Introduction

NHibernate can and will take away the burden of our shoulders. Never again you will have to write and maintain stored procedures. Never again you will deal with ADO.NET and the like. When using a modern ORM tool you can concentrate on the core elements of an application, the ones that provide real business value. You can concentrate on the model of the business domain and the business rules there in. Some people that I respect have even pointed out that if you continue to implement your own data access code you are stealing (money) from your customer.

But this is enough for now. Let's start to introduce NHibernate and its sister Fluent NHibernate. To make this introduction a little bit more realistic let’s first find an interesting domain. The domain should be well known to most of you people. What else could be a better fit that an order entry system? At the same time – to not make things too complicated – I have to define a rather simplistic order entry system. So let's start.

Object Model first approach

One important change to consider when you develop a so called green-field application (a new application) is that you normally start with the object model of the domain for which you want to develop the application. In the past most of the time the data model was developed first. So you started with the entity relationship diagram (ERD). On top of that the application was then built. But in this tutorial we want to first concentrate on the object model and let the database schema be generated automatically by NHibernate based on the object model. Don't misunderstand me; I am not saying that one cannot use NHibernate to develop applications the other way around. NHibernate is also able to deal with the situation where there already is an existing data model and one has to build an application on top of this.

The domain model

In our simplistic domain model customers can place orders. An order is entered into the system by an employee who is responsible for it. Orders consist of one-to-many order items. Each order item defines the quantity of a single product the customer wants to purchase.

image.png

Mapping the domain model

Once we have our domain model in place we want to be able to store the state (of this model) in some place. Very often this is done by using a relational database management system (RDBMS) like SQL Server, Oracle, IBM DB2 or MySql to name just a few. When you are using NHibernate it doesn't really matter which database product you’re going to use since NHibernate supports most of the well known database products. Your application will not be tied to a specific database.

More and more people use it and are very happy with it. In this approach one uses another framework called Fluent NHibernate to define the mapping with the aid of a fluent interface written directly in C#. One often calls this fluent interface an internal DSL. The advantage of using Fluent NHibernate for the mapping is that the mapping is type-safe since it is not based on strings. That makes it also much more refactor-friendly than when using XML documents. Another benefit of this approach is that one can define the mappings in C# which is a full blown programming language and not only a data description language as XML is. This fact opens the door for many - previously unthinkable - possibilities since the mapping can now contain logic. I will discuss this fact in more detail in a later part of the series.

Preparing my system to use NHibernate

NHibernate and Fluent NHibernate are both Open Source projects. Thus their source is freely available to everybody over the internet. The best way to get the most recent version of the projects is to download the source and compile it on your system. Don’t be afraid by what I just said! It is very easy and straight forward as you will see in a minute.

Install an SVN client application

To be able to download the source code from the repository (on the internet) you have to install a so called SVN client. SVN is an OSS source code repository software and is very popular among OSS developers. The probably best and most popular SVN client for Windows is the Tortoise.SVN application. This application is also open source and thus free. Download the installer from the link given below and install it on your computer. You have to reboot after the setup since Tortoise.SVN hooks very deeply into the system. http://tortoisesvn.tigris.org

Once you have done this step you are ready to download NHibernate. But wait a second. We are not going to download NHibernate but rather the Fluent NHibernate project since this project is built on top of NHibernate and thus contains everything we need.

Download Fluent NHibernate

Define a new folder on your system where you want to store the source of the Fluent NHibernate project. As an example let’s say you create a folder c:\dev\FluentNH. Right click with your mouse on this folder and choose the context menu item Checkout (which was added when you installed TortoiseSVN). Enter the URL to the source code repository of Fluent NHibernate. Once you have entered this URL click OK. Now the source code and any libraries needed are downloaded from the web. Depending on the speed of your internet connection this might take a minute or two.

Setup a new solution

Define a new folder for the solution we are going to implement. As an example let’s take c:\dev\NHSample1. In this folder create a sub-folders named lib. This sub-folder will contain all libraries we need to develop and test our application. Copy the files from the Fluent NHibernate build sub-folder to the lib sub-folder.

An even easier way is to just copy the files of the lib folder in the sample code accompanying this tutorial.

Start Visual Studio and create a new project. Navigate to the c:\dev\NHSample1 folder and open it. As a project type choose Class Library and name the project NHSample1. Call the solution src (this is only a temporary name and will be changed immediately. This "trick" helps us to get the desired folder structure). Once the solution is open locate the solution explorer and right click on the solution node and choose Rename. Call the solution NHSample1 as well.

We want to always test our mappings when implementing this solution so let’s immediately add another project to our solution. This will be the project containing all our unit tests. The type of this project should also be Class Library and I usually name it UnitTests.

We are now ready to implement our first NHibernate based solution.

Implementing and mapping the first object of the domain model

As told and justified previously we want to keep our domain model rather simple to be able to concentrate on the relevant factors. Let’s start with the customer object. Add a folder to the NHSample1 project and name it Domain. Add a class named Customer to the Domain folder. The class contains the following code

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string CountryCode { get; set; }
}

Now let's define the mapping for the customer object. Add a folder Mappings to the NHSample1 project. To the Mappings folder add a new class named CustomerMapping. To use the Fluent NHibernate framework for our mappings we have to reference it. So, add a reference to the NHSample1 project and reference the fluent-nhibernate.dll in the lib folder of our solution.

Now we can define the mapping. Our mapping class has to inherit from the ClassMap base class. The mappings are then defined in the constructor of our class. Thus our class will look as follows so far (note that the generic parameter of the base class is the class for which we define the mapping)

public class CustomerMapping : ClassMap<Customer>
{
    public CustomerMapping()
    {
    }
}

Let’s start by mapping the ID of the customer object. A customer is an entity and each entity is uniquely identified by its id. In our case the id is of type int to make it human friendly. We could as well choose a GUID as the type of our id but for this first sample we want to have a human easy readable format. Add the following line of code to the constructor

Id(c => c.Id).GeneratedBy.HiLo("customer");

Note that we have use the HiLo-identity generator of NHibernate. This is the recommended identity generator when using integral ids and having SQL server as a database (it is NOT recommended to use fields of type identity as id fields on Microsoft SQL Server when using NHibernate!).

The part Id(c=>c.ID) tells the system that we want to define the property Id of our customer class to be the id of the entity. Please note the usage of the lambda expression c=>c.Id to define the mapping. This might look a little bit unusual and strange at the beginning especially when compared with a syntax like Id("Id"). But the former has the advantage to be type-safe where the latter is not. When being type-safe the compiler can check whether the mapping is correct or not. If instead we use strings for the mapping the compiler cannot check for us whether we e.g. made a typo. Only at runtime the wrong code will fail.

Now let’s map the remaining properties of the customer object. Those are all of type string. Some of them are mandatory and some are not. All of them have a maximal length. Add the following code to the constructor of the class

Map(c => c.FirstName)
    .Not.Nullable()
    .WithLengthOf(50);

With this statement we define the mapping of the property FirstName. First name is mandatory and thus cannot be null. We also tell the system that its maximal length is 50 characters. If we do not explicitely define the name of the column in the corresponding table on the database will be the same as the name of the mapped property. In our case this is FirstName. Of course this can be changed anytime by using the appropriate syntax.

Our constructor containing the complete mapping will now look like this

public CustomerMapping()
{
     Not.LazyLoad();
     Id(c => c.Id).GeneratedBy.HiLo("customer");
     Map(c => c.FirstName).Not.Nullable().WithLengthOf(50);
     Map(c => c.LastName).Not.Nullable().WithLengthOf(50);
     Map(c => c.AddressLine1).Not.Nullable().WithLengthOf(50);
     Map(c => c.AddressLine2).WithLengthOf(50);
     Map(c => c.PostalCode).Not.Nullable().WithLengthOf(10);
     Map(c => c.City).Not.Nullable().WithLengthOf(50);
     Map(c => c.CountryCode).Not.Nullable().WithLengthOf(2);
}

Note how readable the whole mapping is (compare this with XML!). Imagine now that you rename a property in the customer class. The compiler will immediately notify you of the fact, that the mapping has to be corrected too. If you are using a refactoring tool like Resharper then the tool refactors this automatically for you.

Test the Mapping

Now as responsible developers we have to test our mapping. Thus add a class to the UnitTests project and name it CustomerMapping_Fixture. The Fluent NHibernate framework also offers use some infrastructure code that makes the testing of mapping really simple.

[TestFixture]
public class CustomerMapping_Fixture : FixtureBase
{
    [Test]
    public void can_correctly_map_customer()
    {
        new PersistenceSpecification<Customer>(Session)
            .CheckProperty(c => c.Id, 1001)
            .CheckProperty(c => c.FirstName, "John")
            .CheckProperty(c => c.LastName, "Doe")
            .CheckProperty(c => c.AddressLine1, "1 Road")
            .CheckProperty(c => c.AddressLine2, "P.O.Box 123")
            .CheckProperty(c => c.PostalCode, "78279")
            .CheckProperty(c => c.City, "Austin")
            .CheckProperty(c => c.CountryCode, "US")
            .VerifyTheMappings();
    }
}

This unit test uses the PersistenceSpecification class to test the mapping and when run should succeed. But to make this work we have to implement the FixtureBase base class which sets up our testing environment. I’ll discuss this in the following section.

The Test Fixture Base Class

What we really need is a (NHibernate) session object which we pass as a parameter to the constructor of the PersistenceSpecification class. This is what we will try to provide in the base class. Let me first show the code and then discuss the details

public class FixtureBase
{
    protected SessionSource SessionSource { get; set; }
    protected ISession Session { get; private set; }
    [SetUp]
    public void SetupContext()
    {
        var cfg = Fluently.Configure()
            .Database(SQLiteConfiguration.Standard.InMemory);
        SessionSource = new SessionSource(cfg.BuildConfiguration()
                                             .Properties, new TestModel());
        Session = SessionSource.CreateSession();
        SessionSource.BuildSchema(Session);
    }
    [TearDown]
    public void TearDownContext()
    {
        Session.Close();
        Session.Dispose();
    }
}

Before each test is run the method SetupContext of the FixtureBase class is executed. This is due to the fact that this method is decorated with the [SetUp] attribute defined by NUnit. The first thing we need is a configuration. The configuration is needed by NHibernate and contains such elements as the type of database to access and the details of the connection string needed to open a connection to the respective database. Fluent NHibernate defines helper classes which significantly facilitate the task of providing the correct configuration. In our case we declare that we want to use SQLite as database and that we want to use this database in "in memory" mode.

Once we have the configuration we can define a session source which will be a factory for NHibernate session objects. Every single command executed by NHibernate needs an open session. A NHibernate is similar (but not equal to) a ADO.NET connection. To define a session source we not only need a configuration object but also a class that derives from PersistanceModel and which in the constructor defines where the mapping classes can be found. In our case I have defined such a class and called it TestModel. The TestModel class is very simple and defined as follows

public class 
TestModel : PersistenceModel
{
    public TestModel()
    {
        addMappingsFromAssembly(typeof(CustomerMapping).Assembly);
    }
}

This class as mentioned above is responsible to inform the system of which mapping classes are used. Here I tell it to scan the whole assembly which contains the CustomerMapping class for (other) classes that derive from ClassMap.

The last two lines of code of the SetupContext method of the FixtureBase class presented above open a new session object by using the session source defined previously. This session object is then used to create the database schema with the aid of the session source object. Yes, you have heard it correctly; the database schema is created automatically by NHibernate. The model and the mappings provide sufficient information to NHibernate such as that it is able to automatically create the schema.

That's all what is needed to successfully start unit testing the mappings defined by Fluent NHibernate.

In the next part of this series I'll continue to implement and map the remaining part of the domain model. I'll discuss some more interesting elements of the mapping and the corresponding tests.

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Hey @haig I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Coin Marketplace

STEEM 0.05
TRX 0.33
JST 0.082
BTC 62530.03
ETH 1633.28
USDT 1.00
SBD 0.44