Pages

Saturday, September 5, 2020

NHibernate: Getting started guide - Object-relational mapping for .net core

Introduction

For people that don't know what NHibernate is.

"NHibernate is a mature, open source object-relational mapper for the .NET framework.
It's actively developed, fully featured and used in thousands of successful projects."
Source

This "NHibernate: Getting started guide" post will explain the basics and how to use NHibernate with:
  • .NET Core 3.1
  • NHibernate 5.3.2
  • NUnit 3.12.0
  • SQLite 1.0.113.1
  • NuGet

  • Used techniques and design patterns

  • NHibernate Mapping by code
  • Repository
  • Domain Model
  • Unit testing

  • Source code

    You can find the source code of this post on: https://github.com/gergroen/NHibernate-getting-started-guide

    Post summary

  • Installing NuGet
  • Download NHibernate, SQLite and NUnit with NuGet
  • Create and test a domain model
  • Create and test NHibernate mapping by code
  • Configure NHibernate for SQLite
  • Create and test a NHibernate helper
  • Create and test a repository

  • Installing NuGet

    First install the NuGet Package Manager if it is not installed.

    Download NHibernate, SQLite and NUnit with NuGet

    NHibernate
    Now you can open the "NuGet Package Manager" by clicking with the right mouse button on your project and choose "Manage NuGet Packges..."

    Search for "NHibernate" and click on Install.

    Two references are added to your project "NHibernate" and "Iesi.Collections".
    "Iesi.Collections" is added because "NHibernate" has a dependency on "Iesi.Collections".

    SQLite
    Start the "NuGet Package Manager" again, search for "System.Data.SQLite.Core" and click on Install.

    NUnit
    Start the "NuGet Package Manager" again, search for "NUnit" and click on install.
    To run the unit tests in Visual Studio you can use tools like ReSharper or Testdriven.net.
    If you don't need to run the unit tests in Visual Studio you can download the "NUnit.Runners" via NuGet.
    "NUnit.Runners" allows you to run the unit tests through the command line or a gui.

    Create and test a domain model

    After downloading all the packages we can start with the domain model.
    First we create a simple "Person" domain model with the properties "Id", "FirstName" and a "LastName".
    Also we include the method "GetFullName();".
    using System;
    
    namespace NHibernate.GettingStarted.Model
    {
        public class Person
        {
            public virtual Guid Id { get; set; }
            public virtual string FirstName { get; set; }
            public virtual string LastName { get; set; }
    
            public virtual string GetFullName()
            {
               return string.Format("{0} {1}", FirstName, LastName);
            }
        }
    }
    
    After creating the domain model we create a test class to test the domain model.
    We're using "NUnit" to unit test our domain model.
    using NHibernate.GettingStarted.Model;
    using NUnit.Framework;
    
    namespace NHibernate.GettingStarted.Test
    {
        [TestFixture]
        public class PersonTest
        {
            [Test]
            public void GetFullNameTest()
            {
                var person = new Person
                                 {
                                     FirstName = "Test",
                                     LastName = "Kees"
                                 };
    
                Assert.AreEqual("Test", person.FirstName);
                Assert.AreEqual("Kees", person.LastName);
    
                Assert.AreEqual("Test Kees", person.GetFullName());
            }
        }
    }
    
    

    Create and test NHibernate mapping by code

    You can create the NHibernate mapping for your domain models in different ways. In this example i use NHibernate mapping by code.
    using NHibernate.GettingStarted.Model;
    using NHibernate.Mapping.ByCode;
    using NHibernate.Mapping.ByCode.Conformist;
    
    namespace NHibernate.GettingStarted.Dao
    {
        public class PersonMap : ClassMapping<Person>
        {
            public PersonMap()
            {
                Id(x => x.Id, m => m.Generator(Generators.GuidComb));
                Property(x => x.FirstName);
                Property(x => x.LastName);
            }
        }
    }
    
    To see the result of the mapping we create this unit test.
    The test output is the traditional xml NHibernate mapping.
    using System;
    using System.Xml.Serialization;
    using NHibernate.GettingStarted.Dao;
    using NHibernate.Mapping.ByCode;
    using NUnit.Framework;
    
    namespace NHibernate.GettingStarted.Test
    {
        [TestFixture]
        public class PersonMapTest
        {
            [Test]
            public void CanGenerateXmlMapping()
            {
                var mapper = new ModelMapper();
                mapper.AddMapping<PersonMap>();
    
                var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
                var xmlSerializer = new XmlSerializer(mapping.GetType());
     
                xmlSerializer.Serialize(Console.Out, mapping);
            }
        }
    }
    

    Test output:
    <?xml version="1.0" encoding="Windows-1252"?> 
    <hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="NHibernate.GettingStarted.Model" assembly="NHibernate.GettingStarted" xmlns="urn:nhibernate-mapping-2.2">
      <class name="Person">
        <id name="Id" type="Guid">
          <generator class="guid.comb" />
        </id>
        <property name="FirstName" />
        <property name="LastName" />
      </class>
    </hibernate-mapping>
    

    Configure NHibernate for SQLite

    To configure NHibernate we need to create a "nhibernate.cfg.xml" file and set the Copy to Output Directory to "Copy Always".
    <?xml version="1.0" encoding="UTF-8"?>
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
      <session-factory name="NHibernate.Test">
        <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property>
        <property name="connection.connection_string">Data Source=test.db;Version=3;New=True</property>
        <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
        <property name="show_sql">true</property>
      </session-factory>
    </hibernate-configuration>
    

    Create and test a NHibernate helper

    Now we create a NHibernate helper class to load the configuration and the mapping to create sessions.
    using System.Collections.Generic;
    using NHibernate.Cfg;
    using NHibernate.Cfg.MappingSchema;
    using NHibernate.Mapping.ByCode;
    
    namespace NHibernate.GettingStarted.Dao
    {
        public static class NHibernateHelper
        {
            private static ISessionFactory _sessionFactory;
            private static Configuration _configuration;
            private static HbmMapping _mapping;
    
            public static ISession OpenSession()
            {
                //Open and return the nhibernate session
                return SessionFactory.OpenSession();
            }
    
            public static ISessionFactory SessionFactory
            {
                get
                {
                    if (_sessionFactory == null)
                    {
                        //Create the session factory
                        _sessionFactory = Configuration.BuildSessionFactory();
                    }
                    return _sessionFactory;
                }
            }
    
            public static Configuration Configuration
            {
                get
                {
                    if (_configuration == null)
                    {
                        //Create the nhibernate configuration
                        _configuration = CreateConfiguration();
                    }
                    return _configuration;
                }
            }
    
            public static HbmMapping Mapping
            {
                get
                {
                    if (_mapping == null)
                    {
                        //Create the mapping
                        _mapping = CreateMapping();
                    }
                    return _mapping;
                }
            }
    
            private static Configuration CreateConfiguration()
            {
                var configuration = new Configuration();
                //Loads properties from hibernate.cfg.xml
                configuration.Configure();
                //Loads nhibernate mappings 
                configuration.AddDeserializedMapping(Mapping, null);
    
                return configuration;
            }
    
            private static HbmMapping CreateMapping()
            {
                var mapper = new ModelMapper();
                //Add the person mapping to the model mapper
                mapper.AddMappings(new List<System.Type> { typeof(PersonMap) });
                //Create and return a HbmMapping of the model mapping in code
                return mapper.CompileMappingForAllExplicitlyAddedEntities();
            }
        }
    }

    Database schema test
    using System;
    using NHibernate.Tool.hbm2ddl;
    using NUnit.Framework;
    
    namespace NHibernate.GettingStarted.Dao.Test
    {
        [TestFixture]
        public class SchemaTest
        {
            [Test]
            public void CanGenerateSchema()
            {
                var schemaUpdate = new SchemaUpdate(NHibernateHelper.Configuration);
                schemaUpdate.Execute(Console.WriteLine, true);
            }
        }
    }
    
    Test output:
    create table Person (
      Id UNIQUEIDENTIFIER not null,
      FirstName TEXT,
      LastName TEXT,
      primary key (Id)
    )
    

    Create and test a Repository

    With the NHibernateHelper we can create a person repository to save, get, update and delete persons to the SQLite database.
    First we create a repository interface.
    using System;
    
    namespace NHibernate.GettingStarted.Model
    {
        public interface IPersonRepository
        {
            /// <summary>
            /// Get person entity by id
            /// </summary>
            /// <param name="id">id</param>
            /// <returns>person</returns>
            Person Get(Guid id);
    
            /// <summary>
            /// Save person entity
            /// </summary>
            /// <param name="person">person</param>
            void Save(Person person);
            
            /// <summary>
            /// Update person entity
            /// </summary>
            /// <param name="person">person</param>
            void Update(Person person);
    
            /// <summary>
            /// Delete person entity
            /// </summary>
            /// <param name="person">person</param>
            void Delete(Person person);
    
            /// <summary>
            /// Row count person in db
            /// </summary>
            /// <returns>number of rows</returns>
            long RowCount();
        }
    }
    
    Repository class
    using System;
    using NHibernate.GettingStarted.Model;
    
    namespace NHibernate.GettingStarted.Dao
    {
        public class NHibernatePersonRepository : IPersonRepository
        {
            public void Save(Person person)
            {
                using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(person);
                    transaction.Commit();
                }
            }
    
            public Person Get(Guid id)
            {
                using (ISession session = NHibernateHelper.OpenSession())
                    return session.Get<Person>(id);
            }
    
            public void Update(Person person)
            {
                using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Update(person);
                    transaction.Commit();
                }
            }
    
            public void Delete(Person person)
            {
                using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(person);
                    transaction.Commit();
                }
            }
    
            public long RowCount()
            {
                using (ISession session = NHibernateHelper.OpenSession())
                {
                    return session.QueryOver<Person>().RowCountInt64();
                }
            }
        }
    }
    
    Repository test
    using System.IO;
    using NHibernate.GettingStarted.Model;
    using NHibernate.Tool.hbm2ddl;
    using NUnit.Framework;
    
    namespace NHibernate.GettingStarted.Dao.Test
    {
        [TestFixture]
        public class NHibernatePersonRepositoryTest
        {
            private IPersonRepository _personRepo;
    
            [SetUp]
            public void CreateSchema()
            {
                DeleteDatabaseIfExists();
    
                var schemaUpdate = new SchemaUpdate(NHibernateHelper.Configuration);
                schemaUpdate.Execute(false, true);
    
                _personRepo = new NHibernatePersonRepository();
            }
    
            [Test]
            public void CanSavePerson()
            {
                _personRepo.Save(new Person());
                Assert.AreEqual(1, _personRepo.RowCount());
            }
    
            [Test]
            public void CanGetPerson()
            {
                var person = new Person();
                _personRepo.Save(person);
                Assert.AreEqual(1, _personRepo.RowCount());
    
                person = _personRepo.Get(person.Id);
                Assert.IsNotNull(person);
            }
    
            [Test]
            public void CanUpdatePerson()
            {
                var person = new Person();
                _personRepo.Save(person);
                Assert.AreEqual(1, _personRepo.RowCount());
    
                person = _personRepo.Get(person.Id);
                person.FirstName = "Test";
                _personRepo.Update(person);
    
                Assert.AreEqual(1, _personRepo.RowCount());
                Assert.AreEqual("Test", _personRepo.Get(person.Id).FirstName);
            }
    
            [Test]
            public void CanDeletePerson()
            {
                var person = new Person();
                _personRepo.Save(person);
                Assert.AreEqual(1, _personRepo.RowCount());
    
                _personRepo.Delete(person);
                Assert.AreEqual(0, _personRepo.RowCount());
            }
    
            [TearDown]
            public void DeleteDatabaseIfExists()
            {
                if (File.Exists("test.db"))
                    File.Delete("test.db");
            }
        }
    }
    

    Useful links:

    Source code
    NHibernate documentation 

    7 comments:

    1. Hi Gerard

      Great guide! Thanks so much for putting it all together.

      Minor typo: GetFullName() has an incorrect brace before it, instead of after.

      Something that I did wonder about is my VS2010 wasn't happy with a recognising some of the types. For example in CanGenerateXmlMapping, I needed to change
      "mapper.AddMapping()" to
      "mapper.AddMapping()".
      Similarly in NHibernateHelper.CreateMapping() I needed to change
      "mapper.AddMappings(new List { typeof(PersonMap) });" to
      "mapper.AddMappings(new List { typeof(PersonMap) });".

      Cheers
      Carlos

      ReplyDelete
    2. Sorry...
      "mapper.AddMapping()" to
      "mapper.AddMapping<PersonMap>()".
      Similarly in NHibernateHelper.CreateMapping() I needed to change
      "mapper.AddMappings(new List { typeof(PersonMap) });" to
      "mapper.AddMappings(new List<System.Type> { typeof(PersonMap) });".

      Cheers
      Carlos

      ReplyDelete
      Replies
      1. Thanks for the feedback Carlos. I've updated the post.

        Delete
    3. Thanks! It made it very clear!

      I have a problem tho.

      I was trying to extend the functionality and wanted to add an external data where I have some more complex models but I got the following exception:

      The type or namespace name 'External' could not be found (are you missing a using directive or an assembly reference?)

      which means in my code:

      using External.Infrastructure.Medicine;
      using System;

      namespace NHibernate.GettingStarted.Model
      {
      public interface IMedicineRepository
      {

      Medicine Get(Guid id);
      ...

      does anyone have an idea how can I integrate this external project to my SQLite test project?

      Thanks!
      John

      ReplyDelete
    4. I assume this is a typo: using nhibernate.cfg.xml caused an exception, because the code was searching for hibernate.cfg.xml. Naming that file hibernate.cfg.xml fixes the issue.

      ReplyDelete