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></span>public void CleanUp()
{
CleanUp(HttpContext.Current, _sessionFactory);
}
</span></span></span></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></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></span></span></span>public T RetrieveById(int id)
{
return _sessionManager.Session.Get<T>(id);
}
</span></span></span>public void Update(T objectToUpdate)
{
_sessionManager.Session.Update(objectToUpdate);
_sessionManager.Commit();
}
</span></span></span>public void Create(T objectToAdd)
{
_sessionManager.Session.Save(objectToAdd);
_sessionManager.Commit();
}
</span></span></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.