Click here to Skip to main content
16,018,353 members
Articles / Web Development / ASP.NET

Simplify Database Operations with Generic Fluent NHibernate

,
Rate me:
Please Sign up or sign in to vote.
4.86/5 (6 votes)
30 May 2012CPOL7 min read 78.1K   2.9K   40   20
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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
/// <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.

C#
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.

C#
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.

C#
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).

C#
/// <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

C#
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

C#
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

C#
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

C#
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

C#
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

C#
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

C#
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)


Written By
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.
This is a Organisation

21 members

Written By
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.

Comments and Discussions

 
AnswerImplementation Web API MVC Pin
Member 103355423-Mar-20 13:35
Member 103355423-Mar-20 13:35 
Hi, excelent.

I am trying to implement your solution with MVC Web API but I have a problem with the context of my service.

It marks the following: Event handlers can only link to HttpApplication events during IHttpModule initialization.

Tanks.
GeneralNhibernate queries Pin
J A Srikanth15-Sep-14 10:13
J A Srikanth15-Sep-14 10:13 
QuestionGreat Article Pin
Member 822303931-Jul-13 22:42
Member 822303931-Jul-13 22:42 
AnswerRe: Great Article Pin
Kieran Maltz2-Aug-13 6:06
Kieran Maltz2-Aug-13 6:06 
BugMapping - Task to Role Pin
SilvioDelgado12-Nov-12 10:45
SilvioDelgado12-Nov-12 10:45 
QuestionIs Any Performace Issue? Pin
Vinoth Arun Raj. X11-Oct-12 23:57
Vinoth Arun Raj. X11-Oct-12 23:57 
AnswerRe: Is Any Performace Issue? Pin
youngnacs7-Nov-14 3:23
youngnacs7-Nov-14 3:23 
GeneralRe: Is Any Performace Issue? Pin
Kieran Maltz7-Nov-14 4:39
Kieran Maltz7-Nov-14 4:39 
QuestionWhat if I'm not using ASP.NET? Pin
Mithir18-Jul-12 23:34
Mithir18-Jul-12 23:34 
AnswerRe: What if I'm not using ASP.NET? Pin
Kieran Maltz19-Jul-12 3:41
Kieran Maltz19-Jul-12 3:41 
GeneralRe: What if I'm not using ASP.NET? Pin
Mithir19-Jul-12 21:55
Mithir19-Jul-12 21:55 
GeneralRe: What if I'm not using ASP.NET? Pin
Kieran Maltz20-Jul-12 2:46
Kieran Maltz20-Jul-12 2:46 
QuestionGreat Job Pin
exzkiel2-Jul-12 16:29
exzkiel2-Jul-12 16:29 
AnswerRe: Great Job Pin
Kieran Maltz3-Jul-12 3:14
Kieran Maltz3-Jul-12 3:14 
GeneralIt's great Pin
Moch Lutfi18-May-12 16:56
Moch Lutfi18-May-12 16:56 
Questionvery good Pin
vlado0512-May-12 11:23
vlado0512-May-12 11:23 
AnswerRe: very good Pin
Kieran Maltz13-May-12 4:56
Kieran Maltz13-May-12 4:56 
GeneralMy vote of 5 Pin
Ambalavanar Thirugnanam8-May-12 3:21
Ambalavanar Thirugnanam8-May-12 3:21 
GeneralRe: My vote of 5 Pin
Kieran Maltz10-May-12 4:13
Kieran Maltz10-May-12 4:13 
GeneralVery Clear and Informative Pin
rcarlson-cayen7-May-12 8:25
rcarlson-cayen7-May-12 8:25 

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.