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

Simplify Database Operations with Generic Fluent NHibernate

, , 30 May 2012
Rate this:
Please Sign up or sign in to vote.
This article describes database communication using a generic Fluent NHibernate implementation.

Introduction 

Developing a feature-rich database library can be difficult for large enterprise applications because of multiple requirements. These instructions have been fully tested using the following technologies:

  • .NET Framework 4.0
  • ASP.NET MVC 3 (with Razor)
  • Fluent NHibernate 1.3 and NHibernate 3.2.
  • Microsoft SQL Server 2008 R2 and SQL CE databases.

This article offers up a complete generic implementation of Fluent NHibernate but does not go into advanced detail on some of the additional features that it can provide. Visit Fluent NHibernate's website to get additional information on implementing it. 

Background  

NHibernate is an object-relational mapping software package for the .NET platform that provides code for mapping object-oriented classes to relational database tables. It abstracts the SQL required for interaction with a database so that the developer can focus on the business requirements of the application. Fluent NHibernate provides XML-free mappings for NHibernate, allowing for the same functionality with a cleaner interface using a standard, generic implementation. Fluent NHibernate can be downloaded directly into your project using NuGet, which will check for dependencies then download and reference the files necessary to get started. The command below can be executed in the Package Manager Console to retrieve Fluent NHibernate with its dependencies.  

Install-Package FluentNHibernate –Project Your.Project.Name

Step 1: Create Session Manager

The Fluent NHibernate Session Manager is used to administer the session that the developer will use to connect to the database. The required functionality of the manager is to hold references to the session, implement the unit of work design pattern, manage transactions to the database, and handle cleanup and disposal of the session when necessary. The interface for the Session Manager is shown below, containing the Session that will be used, rollback and commit functions, and a cleanup function.

public interface IFNHSessionManager
{
    ISession Session { get; }

    void CleanUp();
    void Rollback();
    void Commit();
}
 

The Session Manager needs several properties for it to function. It needs to hold a reference to the session factory, the session, and the transaction. The transaction can be private because we are exposing the Rollback and Commit functions later in this class which allow the implementation of the unit of work design pattern. The code that has been added to the Session property is to bind it to the HttpContext. This way, the cleanup and disposal functions can retrieve the bound Session to complete requests.

private readonly ISessionFactory _sessionFactory;

public ISessionFactory SessionFactory
{
    get { return _sessionFactory; }
}

public ISession Session
{
    get
    {
        if (!ManagedWebSessionContext.HasBind(HttpContext.Current, SessionFactory))
        {
            ManagedWebSessionContext.Bind(HttpContext.Current, SessionFactory.OpenSession());
        }
        return _sessionFactory.GetCurrentSession();
    }
}

private readonly ITransaction _transaction;

Next, we need a constructor for the Session Manager that wires everything up. The configuration code can be implemented in a separate class (usually called FNHibernateHelper) but for simplicity, I have included it in the constructor. Another important step when using Fluent NHibernate with an ASP.NET web application is that when you bind the Session to the HttpContext, you also include a line that binds the cleanup function to the EndRequest event.

public FNHSessionManager(string dbConfigKey, DatabaseType dbType)
{
    switch (dbType)
    {
        case DatabaseType.MsSql:
            _sessionFactory = Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008
                        .ConnectionString(dbConfigKey)
                )
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<T>())
                .CurrentSessionContext(typeof(ManagedWebSessionContext).FullName)
                .BuildSessionFactory();
            break;
        case DatabaseType.MsSqlCe:
            _sessionFactory = Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008
                        .ConnectionString(c => c.FromConnectionStringWithKey(dbConfigKey))
                )
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<T>())
                .CurrentSessionContext(typeof(ManagedWebSessionContext).FullName)
                .BuildSessionFactory();
            break;
    }

    if (HttpContext.Current != null && HttpContext.Current.ApplicationInstance != null)
    {
        HttpContext.Current.ApplicationInstance.EndRequest += (sender, args) => CleanUp();
    }

    _transaction = Session.BeginTransaction();
}

The Rollback and Commit functions are the implementation of the unit of work design pattern. As shown below, it allows the developer to run a procedure (Create, Update, Delete, and Retrieve being the common ones) as a single transaction that can be rolled up into one request to the database.

public void Rollback()
{
    if (_transaction.IsActive)
        _transaction.Rollback();
}

public void Commit()
{
    if (_transaction.IsActive)
        _transaction.Commit();
}

Finally, the manager implements the cleanup/dispose functions that complete a transaction at the end of the request. The dispose function implements the Cleanup function and disposes of the current session. This function should only be called at the end event of the application, not at the end of the request. There are two cleanup functions, one generic that allows for any context to which the Session could be bound, but the other specifies the context as the current HttpContext. This would be the more common usage of the Cleanup function.

/// <span class="code-SummaryComment"><summary>
</span>/// Clean up the session.
/// <span class="code-SummaryComment"></summary>
</span>public void CleanUp()
{
    CleanUp(HttpContext.Current, _sessionFactory);
}

/// <span class="code-SummaryComment"><summary>
</span>/// Static function to clean up the session.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="context">The context to which the session has been bound.</param>
</span>/// <span class="code-SummaryComment"><param name="sessionFactory">The session factory that contains the session. </param>
</span>public static void CleanUp(HttpContext context, ISessionFactory sessionFactory)
{
    ISession session = ManagedWebSessionContext.Unbind(context, sessionFactory);

    if (session != null)
    {
        if (session.Transaction != null && session.Transaction.IsActive)
        {
            session.Transaction.Rollback();
        }
        else if (context.Error == null && session.IsDirty())
        {
            session.Flush();
        }
        session.Close();
    }
}

/// <span class="code-SummaryComment"><summary>
</span>/// Dispose of the session factory.
/// <span class="code-SummaryComment"></summary>
</span>public void Dispose()
{
    CleanUp();
    _sessionFactory.Dispose();
}

This concludes the code for the NHibernate Session Manager which implements a class to manage the session and unit of work. This code can be found in the files named IFNHSessionManager.cs and FNHSessionManager.cs.

Step 2: Create Generic Repository

The FNHRepository is used to administer database operations (i.e. Create, Retrieve, Update, and Delete). It contains a reference to the previously (Step 1) developed FNHSessionManager so that it has access to a Session that can execute the specified operation on the database. The interface shown below shows the main operations that NHibernate will provide you when communicating with the database. There are several other features of NHibernate that can be leveraged here, including functionality that leverages LINQ (not discussed in this article). Be sure to notice that the implementation is generic so we don’t have to implement the repository for every object that we create.

public interface IFNHRepository<T>
{
    void Create(T objectToAdd);
    T RetrieveById(int id);
    void Update(T objectToUpdate);
    void Delete(T objectToDelete);
}

Below is the only property contained in the FNHRepository – a reference to the Session Manager developed in Step 1. This property is read only because it can be set in the constructor and for the remainder of the lifetime of this object, it should stay the same.

public readonly IFNHSessionManager _sessionManager;

Next, we need a constructor to create the Repository and provide a reference to the Session Manager object that is created using the database type and connection string that corresponds to the action being requested.

public FNHRepository(IFNHSessionManager sessionManager)
{
    _sessionManager = sessionManager;
}

Finally, the Repository requires implementation to the NHibernate API for communication with the database. The basic functionality implemented here includes CRUD operations (Create, Retrieve, Update, and Delete).

/// <span class="code-SummaryComment"><summary>
</span>/// Retrieve an object instance from the database by id.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="id">Id of the object to retrieve.</param>
</span>/// <span class="code-SummaryComment"><returns>The object instance to use in the application.</returns>
</span>public T RetrieveById(int id)
{
    return _sessionManager.Session.Get<T>(id);
}

/// <span class="code-SummaryComment"><summary>
</span>/// Update an object in the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToUpdate">Object instance containing the information to change in the database.</param>
</span>public void Update(T objectToUpdate)
{
    _sessionManager.Session.Update(objectToUpdate);
    _sessionManager.Commit();
}

/// <span class="code-SummaryComment"><summary>
</span>/// Create an object in the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToAdd">Object instance containing the information to add to the database.</param>
</span>public void Create(T objectToAdd)
{
    _sessionManager.Session.Save(objectToAdd);
    _sessionManager.Commit();
}

/// <span class="code-SummaryComment"><summary>
</span>/// Delete an object from the database.
/// <span class="code-SummaryComment"></summary>
</span>/// <span class="code-SummaryComment"><param name="objectToDelete">Object instance containing the information to delete from the database.</param>
</span>public void Delete(T objectToDelete)
{
    _sessionManager.Session.Delete(objectToDelete);
    _sessionManager.Commit();
}

This concludes the implementation of the NHibernate Repository which allows for operations that communicate with the database. The code contained in this step can be found in IFNHRepository.cs and FNHRepository.cs.

Step 3: Create Objects

Any business objects that are required for the application to run must be created next so that there is a domain with which to work. These objects are the Models that will be used in the web application so there may be a need to further define the objects with data attributes and other functionality. For this example, I have created three classes: Employee, Role, and Task.

Employee.cs

public class Employee
{

    public virtual int Id { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual string Position { get; set; }
    public virtual ICollection<Role> EmployeeRoles { get; set; }

    public Employee()
    {
        EmployeeRoles = new HashSet<Role>();
    }
}

Role.cs

public class Role
{

    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Employee> Employees { get; set; }
    public virtual ICollection<Task> Tasks { get; set; }

    public Role()
    {
        Employees = new HashSet<Employee>();
        Tasks = new HashSet<Task>();
    }
}

Task.cs

public class Task
{

    public virtual int Id { get; set; }
    public virtual string TaskName { get; set; }
    public virtual string Description { get; set; }

    public Task()
    {
    }
}

One thing to note here is that every property that you want to map needs to be set to virtual. Secondly, whenever a collection is used, the property should be defined using the interface and initialized as the concrete class in the constructor.

Step 4: Create Object Mappings

The final step before implementation of the user interface is to define the object mappings so that NHibernate knows what to do with the properties.

EmployeeMap.cs

public class EmployeeMap : ClassMap<Employee>
{

    public EmployeeMap()
    {
        Schema("dbo");
        Table("Employees");

        Id(x => x.Id).Column("EmployeeId");
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.Position);
        HasManyToMany<Role>(x => x.EmployeeRoles).Table("xrEmployeeRoles")
		.ParentKeyColumn("EmployeeId").ChildKeyColumn("RoleId").AsSet()
		.Not.LazyLoad();
    }
}

RoleMap.cs

public class RoleMap : ClassMap<Role>
{

    public RoleMap()
    {
        Schema("dbo");
        Table("Roles");

        Id(x => x.Id).Column("RoleId");
        Map(x => x.Name);
        HasManyToMany<Employee>(x => x.Employees).Table("xrEmployeeRoles")
		.ParentKeyColumn("RoleId").ChildKeyColumn("EmployeeId")
		.Inverse().AsSet().Not.LazyLoad().Cascade.All();
        HasManyToMany<Task>(x => x.Tasks).Table("xrRoleTasks")
		.ParentKeyColumn("RoleId").ChildKeyColumn("TaskId")
		.AsSet().Not.LazyLoad().Cascade.All();
    }
}

TaskMap.cs

public class TaskMap : ClassMap<Task>
{

    public TaskMap()
    {
        Schema("dbo");
        Table("Tasks");

        Id(x => x.Id).Column("TaskId");
        Map(x => x.TaskName);
        Map(x => x.Description);
    }
}

There are several things to note about the mappings. I have specified the Schema and Table explicitly in the mapping to mitigate any confusion about where the data is coming from. Next, my database entities have a different column name as the identities implemented in my C# objects. Because of this, the column name must be specified as a part of the mapping. Since there are two cross reference tables, this has created a many-to-many relationship between these objects. The mapping must reflect the table with which to reference and the parent and child keys in order to properly execute. The many-to-many relationship ends up as a set, so that must be specified in the mapping as well.

Step 5: Utilize Fluent NHibernate

Once the class library above has been implemented, the only thing left is to use it to develop the user interface. Enter a record into the Employee table and replace the 1 in the call to RetrieveById to get your record.

HomeController.cs

FNHSessionManager<Employee> sessionManager = new FNHSessionManager<Employee>(_connString, FNHSessionManager<Employee>.DatabaseType.MsSql);
FNHRepository<Employee> repository = new FNHRepository<Employee>(sessionManager);

Employee emp = repository.RetrieveById(1);

The code sample shown above creates an instance of the SessionManager and passes that to the Repository. Once the Repository has been created, it can be used to interact with the database.

Points of Interest

Fluent NHibernate makes getting data from the database incredibly easy. Creating a web project with a generic database CRUD utility with Fluent NHibernate is as simple as this article documents and makes development fast and easy. If there is interest in a test solution for the code presented, I would be happy to write a follow up article.

History

No changes as of yet.

License

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

Share

About the Authors

Kieran Maltz
Software Developer (Senior) Cayen Systems
United States United States
I am a software engineer based in Milwaukee, Wisconsin. I have been developing software as a hobby since high school and professionally since 2006. I am on a never-ending quest for proper programming principles and clean coding techniques.

Cayen Systems
Cayen Systems
United States United States
Cayen Systems develops online data management software for non-profit and education organizations with a team of experienced and dedicated individuals located in Milwaukee, Wisconsin. We provide the most comprehensive software solutions for information management, tracking and data reporting available today. Specializing in developing easy to use and efficient solutions, we consistently help our clients increase their productivity and reduce the costs of program management.
Group type: Organisation

23 members


Comments and Discussions

 
QuestionGreat Article PinmemberMember 822303931-Jul-13 22:42 
AnswerRe: Great Article PinmemberKieran Maltz2-Aug-13 6:06 
BugMapping - Task to Role PinmemberSilvioDelgado12-Nov-12 10:45 
QuestionIs Any Performace Issue? Pingroupvinoth.arunraj8611-Oct-12 23:57 
QuestionWhat if I'm not using ASP.NET? PinmemberMember 765453418-Jul-12 23:34 
AnswerRe: What if I'm not using ASP.NET? PinmemberKieran Maltz19-Jul-12 3:41 
GeneralRe: What if I'm not using ASP.NET? PinmemberMember 765453419-Jul-12 21:55 
GeneralRe: What if I'm not using ASP.NET? PinmemberKieran Maltz20-Jul-12 2:46 
QuestionGreat Job Pinmemberfatespinnerx2-Jul-12 16:29 
AnswerRe: Great Job PinmemberKieran Maltz3-Jul-12 3:14 
GeneralIt's great PinmemberMoch Lutfi18-May-12 16:56 
thx for sharing Big Grin | :-D
Questionvery good Pinmembervlado0512-May-12 11:23 
AnswerRe: very good PinmemberKieran Maltz13-May-12 4:56 
GeneralMy vote of 5 PinmemberAmbalavanar Thirugnanam8-May-12 3:21 
GeneralRe: My vote of 5 PinmemberKieran Maltz10-May-12 4:13 
GeneralVery Clear and Informative Pingrouprcarlson-cayen7-May-12 8:25 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 30 May 2012
Article Copyright 2012 by Kieran Maltz, Cayen Systems
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid