Click here to Skip to main content
15,867,308 members
Articles / Web Development / IIS

WCF by Example - Chapter XI - NHibernate Implementation

Rate me:
Please Sign up or sign in to vote.
4.92/5 (15 votes)
14 Oct 2011CPOL11 min read 86.9K   46   22
Unit of work pattern on NHibernate using repositories.
PreviousNext
Chapter XChapter XII

The series

WCF by Example is a series of articles that describe how to design and develop a WPF client using WCF for communication and NHibernate for persistence purposes. The series introduction describes the scope of the articles and discusses the architect solution at a high level. The source code for the series is found at CodePlex.

Chapter overview

In chapter X, we discussed how Dependency Injection leverages our design providing an easy mechanism for changing our back-end implementation; we also demonstrated how to configure the application to use the in-memory persistence implementation. Working using the in-memory implementation is very productive, it helps to focus on the object modeling and leaves the heavy and expensive stuff of integrating the application to the back-end database for a later phase when the model is probably at a more stable stage. This approach could derive on having two major phases before the UAT: object modeling and integration (to the back-end database). However, it provides some benefits to have someone looking at the NHibernate implementation at an earlier stage if feasible, sometimes it could be found that model changes are required when entities are persisted to the database. In this process, a good set of tests that can be executed using both implementations is key to ensure an easy transaction.

In this chapter, we introduce the NHibernate implementation to the eDirectory solution. Although a few steps will be required, we expect to show how easily the new components integrate with our existing solution and how from the client application's point of view the change from the in-memory to the NHibernate mode is transparent. In the below diagram, we can observe that the only difference with the in-memory mode is where the TransFactory implementation used is a new NHibernate component. It is worth noting that we keep executing the application in one single process:

Image 3

Setting up the back-end database

Before we can continue, we need to configure a database instance for the eDirectory solution. In this example, we are using SQL Server but you could try with Oracle, MySQL, or even SQLite databases. We are expecting you to have SQL Express installed, but you will find that the NHibernate configuration file provided in this chapter works well on SQL Server 2005/2008 server instances.

You need to create a database named eDirectory, you could create it within Visual Studio following the next steps:

  1. In Server Explorer, select the Create new database option:
  2. Image 4

  3. The local instance in our example was named SQLEXPRESS, we are using Windows Authentication and we named the database eDirectory:
  4. Image 5

Customer configuration mapping file

For each entity (Customer class in our solution), we need to define an NHibernate mapping between the class and the database back-end. In the eDirectory solution, we are going to use XML configuration files, but we could have used NFluent declarations instead.

In the Domain project, we have added a Mappings folder and then the Customer.hbm.xml was added. This is an NHibernate configuration file, you may want to specify the NHibernate schema file definition so intellisense is available:

Image 6

One small detail about the XML file is that it needs to be set as an embedded resource so the NHibernate configuration helper can find it within the assembly at run time:

Image 7

In the first place, we declare the header section with the assembly and namespace details of the Customer class:

Image 8

In the body section, we declare the mapping for the Customer class:

Image 9

It is worth noting how the Id property is declared, we indicate that a SQL Identity field is used for this field, and if the value of the class property is zero, it indicates to NHibernate that the instance has not been persisted. The other three fields are self-explained.

eDirectory.NHibernate project

We need a new project for the NHibernate implementation, we will declare five new classes in this project. Firstly, besides adding references to eDirectory.Common and eDirectory.Domain, we also need references to the NHibernate and NHibernate.Linq assemblies.

We need to implement the same sort of classes that we have in the in-memory eDirectory.Naive assembly:

Image 10

The Repository implementation for NHibernate is simpler that the in-memory one, that might be a surprise for some people:

C#
namespace eDirectory.NHibernate.Repository
{
    public class RepositoryNh<TEntity>
         : IRepository<TEntity>
    {

        private readonly ISession SessionInstance;

        public RepositoryNh(ISession session)
        {
            SessionInstance = session;
        }

        #region Implementation of IRepository<TEntity>

        public TEntity Save(TEntity instance)
        {
            SessionInstance.Save(instance);
            return instance;
        }

        public void Update(TEntity instance)
        {
            SessionInstance.Update(instance);
        }

        public void Remove(TEntity instance)
        {
            SessionInstance.Delete(instance);
        }

        public TEntity GetById(long id)
        {
            return SessionInstance.Get<TEntity>(id);
        }

        public IQueryable<TEntity> FindAll()
        {
            return SessionInstance.Linq<TEntity>();
        }

        #endregion
    }
}

The two most important aspects of the above implementation are how Session resolves all our method implementations and the use of the generic Linq extension method in the FindAll method. This little piece of code provides great flexibility for executing queries without creating dedicated repository methods. It is a little beauty.

Then we have the implementation of the RepositoryLocator; the purpose of this class is the management of repositories and helping to find an instance of a repository for a given entity. What we have done is pushing up to the base class some functionality that we defined in the in-memory implementation that can be re-used with the NHibernate one. In this way, the new class is quite simple:

C#
namespace eDirectory.NHibernate.Repository
{
    public class RepositoryLocatorNh
        :RepositoryLocatorBase
    {
        private readonly ISession SessionInstance;

        public RepositoryLocatorNh(ISession session)
        {
            SessionInstance = session;
        }
        
        #region Overrides of RepositoryLocatorBase

        protected override IRepository<T> CreateRepository<T>()
        {
            return new RepositoryNh<T>(SessionInstance);
        }

        #endregion
    }
}

The TransManager implementation is a little more complex, but maybe the most important aspect is the Session instance that needs to be passed when a new instance of the manager is created. This implementation manages transactions, not like the in-memory implementation:

C#
namespace eDirectory.NHibernate.TransManager
{
    public class TransManagerNh
        :TransManagerBase
    {
        private readonly ISession SessionInstance;

        public TransManagerNh(ISession session)
        {
            SessionInstance = session;
            Locator = new RepositoryLocatorNh(SessionInstance);
        }

        #region Overriden Base Methods

        public override void BeginTransaction()
        {
            base.BeginTransaction();
            if (SessionInstance.Transaction.IsActive) return;
            SessionInstance.BeginTransaction();
        }

        public override void CommitTransaction()
        {
            base.CommitTransaction();
            if (!SessionInstance.Transaction.IsActive) return;
            SessionInstance.Transaction.Commit();
        }

        public override void Rollback()
        {
            base.Rollback();
            if (!SessionInstance.Transaction.IsActive) return;
            SessionInstance.Transaction.Rollback();
        }

        protected override void Dispose(bool disposing)
        {
            if (!disposing) return;
            // free managed resources
            if (!IsDisposed && IsInTranx)
            {
                Rollback();
            }
            Close();
            Locator = null;
            IsDisposed = true;
        }
        
        private void Close()
        {
            if (SessionInstance == null) return;
            if (!SessionInstance.IsOpen) return;
            SessionInstance.Close();
        }

        #endregion
    }
}

The last implementation is the Factory, but before we cover it, we need to introduce a class that manages some infrastructure aspects of NHibernate, one is the configuration settings and the other is the schema exporter helper class. The NhBootStrapper class contains two properties:

C#
namespace eDirectory.NHibernate.BootStrapper
{
    public class NhBootStrapper
    {
        private Configuration NhConfigurationInstance;

        public Configuration NhConfiguration
        {
            get
            {
                if (string.IsNullOrEmpty(ConfigurationFileName))
                    throw new ArgumentException(
                      "ConfigurationFileName property was blank");
                NhConfigurationInstance = new Configuration();
                NhConfigurationInstance.Configure(ConfigurationFileName);
                NhConfigurationInstance.AddAssembly(typeof(Customer).Assembly);
                return NhConfigurationInstance;
            }
        }


        private SchemaExport eDirectorySchemaExportInstance;

        public SchemaExport eDirectorySchemaExport
        {
            get
            {
                if (eDirectorySchemaExportInstance != null)
                    return eDirectorySchemaExportInstance;
                eDirectorySchemaExportInstance = new SchemaExport(NhConfiguration);
                return eDirectorySchemaExportInstance;
            }
        }


        public string ConfigurationFileName { get; set; }
    }
}

We need to set the ConfigurationFileName property before NhConfiguration can be invoked. Calling this property for the first time, an NHibernate configuration class instance is created from the given file and the mappings declared in the eDirectory.Domain assembly are also declared.

eDirectorySchemaExport is not intended to be used by eDirectory production applications, it is there to help in re-generating the eDirectory database schema by auxiliary applications like tests or a console application responsible for the creation of the database schema.

The implementation of the factory class is as follows:

C#
namespace eDirectory.NHibernate.TransManager
{
    public class TransManagerFactoryNh
        : ITransFactory
    {

        private ISessionFactory SessionFactoryInstance;

        private ISessionFactory SessionFactory
        {
            get
            {
                if (SessionFactoryInstance != null)
                    return SessionFactoryInstance;
                SessionFactoryInstance = InitializeSessionFactory();
                return SessionFactoryInstance;
            }
        }

        #region Implementation of ITransFactory

        public ITransManager CreateManager()
        {
            return new TransManagerNh(SessionFactory.OpenSession());
        }

        #endregion

        #region Properties

        public string ConfigurationFileName { get; set; }

        #endregion

        #region Session Factory Initializer

        private ISessionFactory InitializeSessionFactory()
        {
            var nhConfiguration = new NhBootStrapper 
                  {ConfigurationFileName = this.ConfigurationFileName};
            return nhConfiguration.NhConfiguration.BuildSessionFactory();
        }

        #endregion
    }
}

The CreateManager method is responsible for passing a Session instance to the Transaction Manager, which it uses to create an instance of the RepositoryLocator. Session instances are generated from the NHibernate SessionFactory that is created using the mentioned NhBootStrapper class. The ConfigurationFileName property is available so it can be "injected" using Dependency Injection; we will see later how the client uses it.

Client changes

This is the most interesting aspect of the NHibernate implementation. The only thing we need to get the client to work using NHibernate and persist data to the database is a new Spring.Net configuration file. It does not even need to change a line of code. Isn't that good?

The new configuration file is named NhConfiguration.xml; if we compare the new file to the in-memory version, the only change is in the definition of the TransFactory reference:

Image 11

It is in this section where we also declare the configuration file name:

XML
<!-- Transaction Factory -->
<object
    id="TransFactoryRef"
    type="eDirectory.NHibernate.TransManager.TransManagerFactoryNh, eDirectory.NHibernate">
  
  <property name="ConfigurationFileName" value="nhibernate.cfg.xml" />
</object>

NHibernate configuration file

We will just cover the basic aspects of the configuration file; you may want to check the reference documentation for a comprehensive description of each of the configuration settings:

XML
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
    <session-factory name="NHibernate.Test">
        <property name="connection.provider">
          NHibernate.Connection.DriverConnectionProvider
        </property>
        <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
        <property name="connection.driver_class">
          NHibernate.Driver.SqlClientDriver</property>
        <property name="connection.connection_string">
          Server=.\SQLEXPRESS;Database=eDirectory;Integrated Security=SSPI;
        </property>

        <property name="show_sql">false</property>
        <property name="proxyfactory.factory_class">
          NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle
        </property>
        <property name="hbm2ddl.keywords">none</property>
        <property name="cache.use_second_level_cache">true</property>
        <property name="cache.use_query_cache" >true</property>
        <property name="cache.provider_class">
          NHibernate.Cache.HashtableCacheProvider</property>
    </session-factory>
</hibernate-configuration>

You may need to change the connection string if your SQL Server instance name is different from the default SQL Server Express name; also, you may want to enable show_sql to see the SQL statements; unfortunately, this only works when running tests or in Console applications.

Console application - Generate schema

At this stage, we are almost ready to run the client. We created a new configuration setting in our solution to ensure that the correct assemblies are deployed on the client folder:

Image 12

We also changed the client app.config so the application now uses the NHibernate configuration:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="SpringConfigFile" value="file://NhConfiguration.xml" />
  </appSettings>
</configuration>

However, if we try to execute the application at this point, the following exception is thrown:

Image 13

If we examine the internal exception, we will find the following description: {"Invalid object name 'Customer'."}, which means that the Customer table does not exist. We need some mechanism to generate our schema; we could do it by hand, but luckily, NHibernate is capable of generating the database schema from our mapping definitions, which proves to be a good approach in new projects where the database is created from scratch.

In eDirectory, we are providing a Console application that generates the schema; it is called eDirectory.Console. This is currently a very basic application, it consists of a single method and a few lines. Besides, it does a lot of magic for us:

C#
class Program
{
    static void Main(string[] args)
    {
        var nhBootStrapper = new NhBootStrapper
                                 {
                                     ConfigurationFileName = @"nhibernate.cfg.xml"
                                 };

        var connString = nhBootStrapper.NhConfiguration.GetProperty(
                            "connection.connection_string");
        System.Console.WriteLine("Updating database schema for: " + connString);
        nhBootStrapper.eDirectorySchemaExport.Create(true, true);
    }
}

The console application can easily be converted to just another type of client, in line to the WPF or the test projects. For example, in a recent project, we used this approach so a Console application was responsible for the migration of data from a legacy database into the new one using the entities and their persistence functionality.

If the Console application is executed, the following SQL statement is invoked:

SQL
-- Updating database schema for: 
--       Server=.\SQLEXPRESS;Database=eDirectory;Integrated Security=SSPI;

if exists (select * from dbo.sysobjects where id = object_id(N'Customer') 
   and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Customer

create table Customer (
   Id BIGINT IDENTITY NOT NULL,
   FirstName NVARCHAR(50) not null,
   LastName NVARCHAR(50) not null,
   Telephone NVARCHAR(20) not null,
   primary key (Id)
)

Not too bad. Let's try to execute our client once more, enabling the show_sql setting in the NHibernate configuration file. If we create a record for "Joe Bloggs", we can observe that the following statements were executed in our new database:

SQL
NHibernate: INSERT INTO Customer (FirstName, LastName, Telephone) 
  VALUES (@p0, @p1, @p2); select SCOPE_IDENTITY();@p0 = 'Joe', 
  @p1 = 'Bloggs', @p2 = '9999-8888'
NHibernate: SELECT this_.Id as Id0_0_, this_.FirstName as FirstName0_0_, 
  this_.LastName as LastName0_0_, 
  this_.Telephone as Telephone0_0_ FROM Customer this_

You may want to open the database to check that the record was effectively created:

Image 14

Tests re-factor

We have created a few tests so far using the in-memory mode, and in many projects this would be sufficient; although in some projects, end-to-end tests are also created to ensure that functionality is correct when using a database. But invoking tests against a database could be somehow expensive in many different ways. In our eDirectory solution, we are in a position to demonstrate how it is possible to have the same test running against the in-memory and NHibernate modes.

There are couple aspects that require a little more effort when dealing with a database and tests. The first one is the re-creation of the data before the test can start. The second is the tear-down of all the changes before the next test can be executed. There are different approaches to this problem but in the eDirectory application, we are going to propose one simple solution: every test will re-create the whole database from scratch. This could be taken by some as a horrible solution to our problem, but it is simple and works well; it may just not perform that well.

What we propose is a development methodology where developers create and execute the tests in in-memory mode, then a Continuous Integration server could be in charge of executing the same tests against a database.

There are a few things to add to our test project to get it to work with NHibernate. In the first place, we need to add a few references and the same configuration files that were added to the client project. We also need a reference to the new eDirectory.NHibernate project. Then we need to modify the app.config to indicate that we are using the NHibernate configuration. For those like me that use ReSharper, they may notice that some additional work is required to ensure that configuration files are deployed to the correct location where the MS tests are executed.

The key is to ensure that tests are not changed because we are running in in-memory or NHibernate mode; the eDirectoryTestBase base class can provide exactly what we need, we just need to add a new method that ensures the database is created at the start of each test:

C#
namespace eDirectory.UnitTests
{
    [TestClass]
    [DeploymentItem("nhibernate.cfg.xml")]
    public abstract class eDirectoryTestBase
    {
        [TestInitialize]
        public virtual void TestsInitialize()
        {
            InitialiseDatabase();
        }

        private static void InitialiseDatabase()
        {
            var nHFactory = 
              GlobalContext.Instance().TransFactory as TransManagerFactoryNh;
            if (nHFactory == null) return;
            var nhBootStrapper = 
                new NhBootStrapper {ConfigurationFileName = 
                nHFactory.ConfigurationFileName};
            nhBootStrapper.eDirectorySchemaExport.Create(false, true);
        }

        [TestCleanup]
        public virtual void TestCleanUp()
        {
            ResetLocator();
        }

        private static void ResetLocator()
        {            
            ...
        }
    }
}

The InitialiseDatabase checks if the tests are executed in NHibernate mode; if they are, then eDirectorySchemaExport is used to re-generate the database schema at the start of the test. It can be seen that the tests take a little longer, but having them be executed against the database is worthwhile:

Image 15

If we set the show_sql option enabled, we can retrieve the SQL statements executed in each test; for example, the following statements are executed when the UpdateCustomer test is executed:

SQL
CustomerServiceTests.UpdateCustomer : Passed

NHibernate: INSERT INTO Customer (FirstName, LastName, Telephone) 
  VALUES (@p0, @p1, @p2); select SCOPE_IDENTITY();@p0 = 'Joe', 
  @p1 = 'Bloggs', @p2 = '9999-8888'
NHibernate: SELECT customer0_.Id as Id1_0_, customer0_.FirstName as FirstName1_0_, 
  customer0_.LastName as LastName1_0_, customer0_.Telephone 
  as Telephone1_0_ FROM Customer customer0_ WHERE customer0_.Id=@p0;@p0 = 1
NHibernate: UPDATE Customer SET FirstName = @p0, LastName = @p1, 
  Telephone = @p2 WHERE Id = @p3;@p0 = 'Joe', @p1 = 'Bloggs', 
  @p2 = '8888-8888', @p3 = 1

Chapter summary

In this chapter, we have demonstrated how NHibernate can be easily integrated to the eDirectory solution using the Dependency Injection functionality that was introduced in the previous chapter. We had not changed one line of code in the client to get the application working with a database. We also explained how to leverage the NHibernate functionality to create our schema from our domain mappings, and we also discussed how to enhance our in-memory tests so they can be executed against a database without changes.

We may have not covered any complex scenarios of persistence for inheritance, parent-child or many-to-many relationships; but we hope we demonstrated how relatively easy it is to integrate the solution components to a database using NHibernate. The Repository, RepositoryLocator, TransactionManager, and Factory classes can perfectly be used in a comprehensive enterprise solution without or a little changes.

In the next chapter, we will discuss the WCF implementation and see for the first time how our application is decoupled between two instances: the client and the server.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalamazing guide 5 Pin
lekeds13-Jun-13 9:45
professionallekeds13-Jun-13 9:45 
GeneralRe: amazing guide 5 Pin
Enrique Albert17-Jun-13 3:49
Enrique Albert17-Jun-13 3:49 
QuestionGreat job !!! Pin
Ldeville10-Jun-12 23:43
Ldeville10-Jun-12 23:43 
AnswerRe: Great job !!! Pin
Enrique Albert11-Jun-12 0:34
Enrique Albert11-Jun-12 0:34 
GeneralRe: Great job !!! Pin
Ldeville11-Jun-12 21:44
Ldeville11-Jun-12 21:44 
Yes, it's exactly for this purpose. In my future project, I think use Tranformers in NHibernate for extract specifics DTO.

Thank you very much for your help.
GeneralRe: Great job !!! Pin
Ldeville13-Jun-12 23:48
Ldeville13-Jun-12 23:48 
GeneralRe: Great job !!! Pin
Enrique Albert14-Jun-12 13:06
Enrique Albert14-Jun-12 13:06 
QuestionPlease help mapping to legacy database Pin
caosnight6-Dec-11 15:09
caosnight6-Dec-11 15:09 
AnswerRe: Please help mapping to legacy database Pin
Enrique Albert6-Dec-11 21:35
Enrique Albert6-Dec-11 21:35 
Generallink to next chapter (12) does not work Pin
Afshar Mohebbi9-Jun-11 1:53
Afshar Mohebbi9-Jun-11 1:53 
GeneralRe: link to next chapter (12) does not work Pin
Enrique Albert16-Oct-11 14:04
Enrique Albert16-Oct-11 14:04 
GeneralMy vote of 5 Pin
alrhr20-Nov-10 7:18
alrhr20-Nov-10 7:18 
GeneralRe: My vote of 5 Pin
Enrique Albert20-Nov-10 12:33
Enrique Albert20-Nov-10 12:33 
GeneralCollections in DTOs, Databinding, ObservableCollections Pin
alrhr18-Nov-10 23:19
alrhr18-Nov-10 23:19 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
Enrique Albert20-Nov-10 2:40
Enrique Albert20-Nov-10 2:40 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
alrhr20-Nov-10 7:14
alrhr20-Nov-10 7:14 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
STL720-Nov-10 11:04
STL720-Nov-10 11:04 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
Enrique Albert20-Nov-10 12:31
Enrique Albert20-Nov-10 12:31 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
Enrique Albert30-Nov-10 3:39
Enrique Albert30-Nov-10 3:39 
GeneralRe: Collections in DTOs, Databinding, ObservableCollections Pin
alrhr3-Dec-10 10:11
alrhr3-Dec-10 10:11 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira3-Nov-10 11:14
mvaMarcelo Ricardo de Oliveira3-Nov-10 11:14 
GeneralRe: My vote of 5 Pin
Enrique Albert3-Nov-10 15:49
Enrique Albert3-Nov-10 15:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.