Click here to Skip to main content
Click here to Skip to main content

Simple NHibernate Architecture

By , 11 Sep 2012
 

Introduction

I started using NHibernate at the end of last year and I am having a wonderful experience with it. The simple fact that I don't have to maintain hundreds of procedures and data access abstraction classes to do basic CRUD operations is enough to justify the use of NHibernate (even though I used a custom code generation tool). Besides that, my code is amazingly clearer and simpler. I only need to worry about business and user interface logic now. All that pluming code is gone for good.

I had zero experience with ORM frameworks, so on the first couple of weeks using NHibernate I had a hard time with some issues, like using it with ASP.NET disconnected objects and abstracting NHibernate sessions from the domain layer. After putting in some effort I came out with a nice architecture that abstracts the NHibernate layer and allows me to work with an ASP.NET session.

Background

I am assuming you already have some familiarity with NHibernate. If you are new to NHibernate I encourage you to go to the website and download the reference documentation. I am using NHibernate 1.2 CR1 in this article.

The Architecture

The idea here is to keep my business objects unaware of an NHibernate session. So I created a different project for NHibernate stuff, called Shared.NHibernateDAL. It includes the session and transaction manager classes. I also created a common business object that serves as a base class for all my business entities. This class (BusinessObject) encapsulates the common methods and properties all entities that need to be persisted have, like Save(), Delete(), ID, etc.

Here's a class diagram just to give an idea:

Screenshot - nh_classdiagram.jpg

The Implementation

The main classes on the NHibernateDAL project are the following:

  • NHibernateSessionManager – This is the class responsible for managing the NHibernate session. I've got this class from another NHibernate article on Code Project.

  • TransactionBlock – This is a class I created to manage transactions and abstract it from NHibernate. I will show an example of its use further on this article.

  • BusinessObject<T> - This is the base class for all the business entities I need to persist. It implements the basic CRUD methods.

Here's the code:

namespace Shared.NHibernateDAL
{
    public class BusinessObject<T>
    {
        public virtual void Save()
        {
            ISession session = NHibernateSessionManager.Instance.GetSession();
            if (!this.IsPersisted)
                session.Save(this);
            else
                session.SaveOrUpdateCopy(this);

            session.Flush();
        }

        public virtual void Delete()
        {            
            ISession session = NHibernateSessionManager.Instance.GetSession();
            session.Delete(this);
            session.Flush();
        }

        public virtual T Clone()
        {            
            return this.Clone();
        }

        /// <summary>

        /// Returns a valid populated object or a null reference        
        /// </summary>

        public static T Get(int id)
        {
            if (id <= 0)
                return default(T);
            else
            {
                ISession session = 
                    NHibernateSessionManager.Instance.GetSession();
                return session.Get<T>(id);
            }
        }

        private int _id;

        public virtual int ID
        {
            get { return _id; }
            set { _id = value; }
        }

        public virtual bool IsPersisted
        {
            get
            {
                return this.ID > 0;
            }
        }
    }
}

Using the Code

On this sample we are going to use a Product class to show the features.

Here's the Product class code:

namespace Business
{
public class Product : BusinessObject<Product>
{
    public Product()
    {
        this._name = null;
        this._weight = 0;
    }

    private string _name;
    private decimal _weight;

    public virtual decimal Weight
    {
        get { return _weight; }
        set { _weight = value; }
    }

    public virtual string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public static List<Product> Select()
    {
        ISession session = NHibernateSessionManager.Instance.GetSession();
        IQuery query = session.CreateQuery("from Product");
        return (List<Product>)query.List<Product>();
    }
}
}

Because all CRUD functionality is encapsulated on the BusinessObject, the domain entities are as simple as that. Now our Product class is ready to be consumed.

Note that I have included a static Select method that returns a list of products. This might be useful for populating user interface controls, but if you want to completely abstract NHibernate from your domain objects you should create another layer of abstraction under the business layer, containing all HQL queries. But, for small projects, I think it is OK to use HQL inside business objects.

The download file contains three projects, corresponding to the three layers I am using: NHibernateDAL, Business and Web. The Web project is a simple web page showing how easy it is to consume CRUD operations from the Product class.

Transactions

It is often necessary to use nested transactions with objects. You might have a method that opens its own transaction and sometimes needs to be called from another transaction. To solve this problem and also abstract the NHibernate transaction methods I've come out with the TransactionBlock class. This class keeps track of how many transaction blocks were open and ensures that only one transaction is opened on the database. At the end it commits the transaction only if all blocks were declared valid. Here's an example of its usage:

public void OutterTransaction()
{
    using (TransactionBlock tran = new TransactionBlock())
    {
        Product p1 = new Product();
        p1.Name = "Product 1";
        p1.Weight = 10;
        p1.Save();

        //Call another method with a transaction block
        InnerTransaction();

        tran.IsValid = true;
        // On the dispose call the database transaction is commited 
        //if all transaction blocks are valid
    }
}

private void InnerTransaction()
{
    using (TransactionBlock tran = new TransactionBlock())
    {
        Product p2 = new Product();
        p2.Name = "Product 2";
        p2.Weight = 10;
        p2.Save();                
        tran.IsValid = true;
    }
}

Conclusion

NHibernate has definitely helped me improve the quality and readability of my code. Combined with code generation tools (like MyGeneration), it can really improve productivity. But be aware, NHibernate and the architecture I am using will not fit every project. Working with legacy database can be a pain, although version 1.2 supports communication with stored procedures.

Any feedback will be greatly appreciated.

License

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

About the Author

Cassio Mosqueira
Software Developer (Senior)
Canada Canada
Member
I've been developing .NET enterprise applications since 2000.
 
I am originally from Rio de Janeiro and I am currently working at http://www.intelligentcoder.com in Ontario.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5membercsharpbd2 May '13 - 20:59 
Nice!!!
GeneralMy vote of 5memberMember 313707823 Sep '12 - 20:03 
Great!!
QuestionListing product from Business Classmemberbjorkenne17 Sep '12 - 8:54 
Hi, I'm just a little confused. Why did you included the listing of product data operation from consuming class instead of the business object class itself?
 
Thanks in advance

Andraly Ng

GeneralMy vote of 5memberMonjurul Habib11 Sep '12 - 9:45 
good work
GeneralMy vote of 4memberShahriar Iqbal Chowdhury2 Jul '10 - 22:54 
Good article
QuestionI test the code but I have a problemmemberbenkhaled lotfi26 Mar '09 - 5:38 
[FileNotFoundException: Impossible de charger le fichier ou l'assembly 'Business' ou une de ses dépendances. Le fichier spécifié est introuvable.]
System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +54
System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +211
System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +141
System.Reflection.Assembly.Load(String assemblyString) +25
NHibernate.Cfg.Configuration.AddAssembly(String assemblyName) +62
 
[MappingException: Could not add assembly Business]
NHibernate.Cfg.Configuration.LogAndThrow(Exception exception) +62
NHibernate.Cfg.Configuration.AddAssembly(String assemblyName) +116
Shared.NHibernateDAL.NHibernateSessionManager.InitSessionFactory() in C:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\NHibernateDAL\NHibernateSessionManager.cs:68
Shared.NHibernateDAL.NHibernateSessionManager..ctor() in C:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\NHibernateDAL\NHibernateSessionManager.cs:40
Shared.NHibernateDAL.Nested..cctor() in C:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\NHibernateDAL\NHibernateSessionManager.cs:49
 
[TypeInitializationException: Une exception a été levée par l'initialiseur de type pour 'Nested'.]
Shared.NHibernateDAL.NHibernateSessionManager.get_Instance() in C:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\NHibernateDAL\NHibernateSessionManager.cs:31
Shared.NHibernateDAL.BusinessObject`1.Save() in C:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\NHibernateDAL\BusinessObject.cs:11
_Default.Page_Load(Object sender, EventArgs e) in c:\Users\lotfi\Documents\Visual Studio 2008\Projects\test3\test3\Default.aspx.cs:20
System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15
System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +33
System.Web.UI.Control.OnLoad(EventArgs e) +99
System.Web.UI.Control.LoadRecursive() +47
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1436
GeneralNew to NHibernate where is the NHibernate database filememberarigney12 Feb '08 - 17:51 
I guys I have been using LLBLGen and LLBLGen Pro so I am new to NHibernate and Active Record.
Actually I could not get the castle projects Mono to compile so I looked here. I think they need to update it.
Can you tell me where I can get this NHibernate database from that you are using? Or do I just create one in SQL and create a product table, then what goes into it?
 
Regards,
Alistair
 
Alistair Rigney
Software Engineer
Choose the appropriate technology before you do anything and check codeproject, sourceforge if someone has done it before.

GeneralWatch out for CallContext in TransactionBlockmemberckinnan16 Aug '07 - 12:00 
On a different note, I just found your article and wanted to let you know that I liked your solution to the nested transaction problem. Providing the commit and rollback code in the dispose keeps the code a bit cleaner what with not needing the try catch and explicit begin, commit, rollback. Good job. We also implemented a transaction stack mechanism, but it relies on a more traditional try catch block.
 
To address the subject of this comment though, CallContext is not necessarily a safe place to put per thread values in a web environment. I'm not sure if that's what you're doing, but I figured I would pass this on as it caused us a bunch of problems in production. The problem is that a single web request is not guaranteed to live in a single thread context. I can't do the subject of context switching justice even if I took the time, but I'll drop a couple of links below.
 
Our solution was to check if HttpContext.Current != null. If it exists store items in HttpContext.Items, otherwise, use the CallContext: (sorry for the VB)
 
	
<System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, _ 
flags:=System.Security.Permissions.SecurityPermissionFlag.Infrastructure)> _
Public NotInheritable Class ContextCache
 
	Private Sub New()
 
	End Sub
 
	Public Shared Function GetData(ByVal key As String) As Object
		If System.Web.HttpContext.Current IsNot Nothing Then
			Return System.Web.HttpContext.Current.Items.Item(key)
		Else
			Return System.Runtime.Remoting.Messaging.CallContext.GetData(key)
		End If
	End Function
 
	Public Shared Sub SetData(ByVal key As String, ByVal value As Object)
		If System.Web.HttpContext.Current IsNot Nothing Then
			If System.Web.HttpContext.Current.Items.Contains(key) Then
				System.Web.HttpContext.Current.Items(key) = value
			Else
				System.Web.HttpContext.Current.Items.Add(key, value)
			End If
		Else
			System.Runtime.Remoting.Messaging.CallContext.SetData(key, value)
		End If
	End Sub
 
End Class
http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html[^] Good overall description of the context switching problem.
http://msdn.microsoft.com/msdnmag/issues/05/01/ASPNETPerformance/#S6[^] Microsoft's recommendation at #5.
 
Hope you find this useful.
GeneralRe: Watch out for CallContext in TransactionBlockmemberCassio Alves17 Aug '07 - 4:59 
Hi,
 
Thank you for the info.
 
From what I was reading this issue was introduced in ASP.NET 2.0. The funny thing is that I've seen a lot of projects relying on CallContext.
 
I'll try to use your suggestion here. The downside is that it needs a reference to System.Web, which is kind of ugly for desktop apps. But it seems it's the only solution available.
 

 


 
Regards,
Cassio Alves
Cool | :cool:

GeneralRe: Watch out for CallContext in TransactionBlockmemberChuck Kinnan17 Aug '07 - 6:14 
You are correct. It's not the prettiest. On the plus side, since System.Web lives in the GAC, at least the assembly is not crufting up the bin directory of your desktop applications.
 
The primary service that we offer currently runs with a combination of the main web site, a second web site for our web API, three services and a newly added desktop application. This approach is working well for us.
 
If you're worried about the footprint of your windows applications, I suppose you could base the if statement off of a config value instead of HttpContext.Current != null, or use a factory method to create a wrapper for the two different approaches. That way System.Web would not be loaded unless you really were in a web app. Avoiding the reference didn't seem all that necessary to us though.
 
Take care

 
Chuck

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 11 Sep 2012
Article Copyright 2007 by Cassio Mosqueira
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid