Using the NHibernate Repository Pattern in C# ASP .NET

Introduction

A large variety of ORM object relational mapping tools exist for use with developing C# ASP .NET web applications. Some of the more popular ORM tools include LINQ to SQL, the Entity Framework, and NHibernate. While previous articles have discussed developing web applications with LINQ to SQL and the Entity Framework through the usage of the business object generator, Linquify, in this tutorial, we’ll focus on implementing the repository pattern for an NHibernate web application.

Repository Pattern vs Business Objects

Before discussing the details of implementing the repository pattern with NHibernate, it’s important to note the difference between the repository model of data storage versus the business object model.

The repository model consists of an individual class which accepts and returns type objects, including NHibernate DTO types, to store and retrieve in the database. The repository itself contains all methods and code for working directly with NHibernate.

The business object model provides database storage and retrieval methods directly on each NHibernate type, usually via inheritance. A brief example highlights the core differences as shown below.

The Repository Model

The NHibernate ORM repository model provides a repository class, which handles all method calls for saving and loading NHibernate types in the database. An example of calling the repository class appears as follows:

1
2
3
4
5
6
7
Repository repository = new Repository();

Person person = new Person();
person.Name = "John Doe";

repository.Save(person);

Notice in the above code, we’ve instantiated a Person class, populated its values, and then passed the person to the repository for saving. In this case, the Person class is a strict DTO data transfer object and contains no data access methods. All NHibernate types in this scenario would be passed to the repository to execute NHibernate database methods.

The Business Object Model

The NHibernate business object model provides methods on each NHibernate type for saving and loading in the database. An example of calling the NHibernate methods on a business object type appears as follows:

1
2
3
4
Person person = new Person();
person.Name = "John Doe";
person.Save();

Notice in the above code, we’ve instantiated a Person class, just as we did with the repository model. However, since each NHibernate type contains the necessary NHibernate method calls, we no longer need a repository class. We can directly call NHibernate methods from each type, such as person.Save(), person.Delete(), person.Find(), etc. In this model, each NHibernate type can inherit from a business class base type, which provides the required methods.

Taking the Repository One Step Further

One drawback to the Business Object model is the lack of sharing support for database connections (NHibernate session) and transactions. Since each NHibernate type in the business object model has their own copy of NHibernate methods, it may be more cumbersome to share the NHibernate session and transaction between types. We can resolve this with the repository model, allowing us to utilize a transaction enabled repository pattern as shown in the following example.

A Transaction Enabled Repository Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using (RepositoryBase repository = new RepositoryBase())
{
try
{
repository.BeginTransaction();

Person person = (Person)repository.GetById(typeof(Person), 12);
person.Age += 1;

repository.Save(person);
}
catch
{
repository.RollbackTransaction();
}
}

Notice in the above code, we’ve instantiated an NHibernate repository pattern within a “using” clause. This allows us to automatically dispose of the repository once we’ve completed our processing. As you’ll see below, our repository’s Dispose() method, flushes the NHibernate session, commits transactions, closes the session, and handles cleaning. As you can tell, the repository pattern really speeds up the integration of NHibernate’s methods with our C# ASP .NET web applications. The above code is using an NHibernate transaction via the repository pattern, but using a transaction is optional. You could leave out the transaction commands, in which case NHibernate’s standard session will be used. Let’s move on to implementing the NHibernate Repository Pattern in C#.

The Repository Interface

To begin the repository pattern, we’ll start with an interface. By providing an interface for the repository pattern to implement, we setup the core framework for the repository design pattern, which can be utilized by a business class layer, along with test driven development or other types of repositories.

1
2
3
4
5
6
7
8
public interface IRepository
{
void Save(object obj);
void Delete(object obj);
object GetById(Type objType, object objId);
IQueryable<TEntity> ToList<TEntity>();
}

Our repository interface is fairly simple. It contains four basic methods for saving, deleting, loading, and listing NHibernate entities.

The NHibernate SessionFactory Manager

Before implementing the IRepository interface, we’ll need a utility class to help manage the NHibernate SessionFactory. Since the SessionFactory can utilize a degree of resources, we’ll want to make the manager class static, allowing our repository to utilize the session as required, without having to recreate it each time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using NHibernate;
using NHibernate.Cfg;

public static class Database
{
private static ISessionFactory _sessionFactory;

private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
var configuration = new Configuration();

configuration.Configure();
configuration.AddAssembly("NHibernateTest.Types");

_sessionFactory = configuration.BuildSessionFactory();
}

return _sessionFactory;
}
}

public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}

The above Database class is a generic NHibernate SessionFactory manager, allowing us access to the session by calling the OpenSession() method. The method calls the NHibernate OpenSession() method on the static SessionFactory object, returning a valid NHibernate session. Note, the “NhibernateTest.Types” is the name of your DTO types assembly where you’ve defined the NHibernate types and XML files (*.hbm.xml).

The Repository Class

The repository class is where the NHibernate method handling is provided. NHibernate DTO types will be passed into this class for data storage or retrieval. We implement the class, based upon the repository interface, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
using NHibernate;
using NHibernate.Linq;

public class RepositoryBase : IRepository, IDisposable
{
protected ISession _session = null;
protected ITransaction _transaction = null;

public RepositoryBase()
{
_session = Database.OpenSession();
}

public RepositoryBase(ISession session)
{
_session = session;
}

#region Transaction and Session Management Methods

public void BeginTransaction()
{
_transaction = _session.BeginTransaction();
}

public void CommitTransaction()
{
// _transaction will be replaced with a new transaction // by NHibernate, but we will close to keep a consistent state.
_transaction.Commit();

CloseTransaction();
}

public void RollbackTransaction()
{
// _session must be closed and disposed after a transaction // rollback to keep a consistent state.
_transaction.Rollback();

CloseTransaction();
CloseSession();
}

private void CloseTransaction()
{
_transaction.Dispose();
_transaction = null;
}

private void CloseSession()
{
_session.Close();
_session.Dispose();
_session = null;
}

#endregion

#region IRepository Members

public virtual void Save(object obj)
{
_session.SaveOrUpdate(obj);
}

public virtual void Delete(object obj)
{
_session.Delete(obj);
}

public virtual object GetById(Type objType, object objId)
{
return _session.Load(objType, objId);
}

public virtual IQueryable<TEntity> ToList<TEntity>()
{
return (from entity in _session.Linq<TEntity>() select entity);
}

#endregion

#region IDisposable Members

public void Dispose()
{
if (_transaction != null)
{
// Commit transaction by default, unless user explicitly rolls it back.
// To rollback transaction by default, unless user explicitly commits, // comment out the line below.
CommitTransaction();
}

if (_session != null)
{
_session.Flush(); // commit session transactions
CloseSession();
}
}

#endregion
}

The first item to note is that the repository implements our IRepository interface, which provides the basic Save, Load, Delete, List methods. It also implements the IDisposable interface, which is key to allowing us to use the “using” clause with our repository for easy clean up and maintenance of NHibernate database connections.

We utilize two private members in our repository class. One member for managing the NHibernate session and another member for managing an NHibernate transaction. The constructor of our class initializes the NHibernate session. You can create a new NHibernate session by using the empty constructor or pass in an existing session to re-use the same NHibernate context and connection.

To utilize transactions with our repository class, we provide the BeginTransaction(), CommitTransaction(), and RollbackTransaction() methods. Note, with the default implementation above, CommitTransaction() will be called automatically in the Dispose() method. For those who wish to cancel a transaction by default, rather than committing, you can remove the call to CommitTransaction() in the Dispose() method, as commented in the code.

The remainder of the functions for saving, loading, listing, etc call the standard NHibernate methods along with the NHibernate DTO types. The methods are designed to be generic, accepting any variety of DTO objects. Note, the ToList() method returns an IQueryable interface, in order to provide LINQ style manipulation of data prior to SQL query execution, so the NHibernate LINQ reference will be needed. In addition, you could add query functions to the repository.

The final item of note in the NHibernate Repository Pattern is the Dispose() method. This method first checks if a transaction is being used, and if so, commits the transaction by default. It then flushes the session, committing any remaining queries, closes the session, and disposes of the session and remaining objects. By utilizing the repository pattern within a “using” clause, this happens automatically.

Calling the NHibernate Repository Pattern

With the repository pattern defined, we can utilize it as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using (RepositoryBase repository = new RepositoryBase())
{
try
{
repository.BeginTransaction();

Person person = (Person)repository.GetById(typeof(Person), 12);
person.Age += 1;

Address address = (Address)repository.GetById(typeof(Address), "New York");
person.Address = address;

repository.Save(person);
}
catch
{
repository.RollbackTransaction();
}
}

The NHibernate repository can also be used with a shared session, across multiple classes in the C# ASP .NET web application as follows:

1
2
3
4
5
6
7
8
9
10
11
12
// Open an NHibernate session in a different web page's class.
ISession mySession = Database.OpenSession();

// Perform some work with mySession.
// ...

// On another page, use the repository pattern with the same session.
using (RepositoryBase repository = new RepositoryBase(mySession))
{
...
}

Test Driven Development TDD Repository Pattern

Since we’ve defined a repository interface, we have the flexibility to create multiple repositories, such as a test repository to work with test driven development. Note, for simplicity in the article, we’ve focused strictly on the NHibernate repository pattern, but we’ve left out an important logical piece of the repository pattern, which is the implementation of a business layer that would receive the concrete repository type and return objects from. The business layer would accept an IRepository type, allowing us to pass any implementation of the repository. For more details on implementing the repository pattern, view Implementing the Repository Pattern.

A Test Repository Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class RepositoryTest : IRepository
{
#region IRepository Members

public void Save(object obj)
{
// Assume save success.
}

public void Delete(object obj)
{
// Assume delete success.
}

public object GetById(Type objType, object objId)
{
// Get it's constructor
ConstructorInfo constructor = objType.GetConstructor(new Type[] { });

// Invoke it's constructor, which returns an instance.
object createdObject = constructor.Invoke(null);

return createdObject;
}

public IQueryable<TEntity> ToList<TEntity>()
{
List<TEntity> resultList = new List<TEntity>();

Type objType = typeof(TEntity);

// Get it's constructor
ConstructorInfo constructor = objType.GetConstructor(new Type[] { });

// Invoke it's constructor, which returns an instance.
object createdObject = constructor.Invoke(null);

resultList.Add((TEntity)createdObject);

return resultList.AsQueryable();
}

#endregion
}

In the above code for a test repository, we’ve implemented the IRepository interface, and provided bodies for the Save, Delete, Load, and List methods. Save and Delete simply assume a successful result. Load instantiates the passed-in object and returns it, simulating a load from the database. ToList functions in a similar manner and instantiates a single passed-in object type and adds it to the list of entities to be returned, simulating a List from the database.

We can then use the two repositories as follows:

1
2
3
4
5
6
7
8
9
10
11
// Using an actual database repository.
IRepository repository = new RepositoryBase();

Person person = (Person)repository.GetById(typeof(Person), 12);
person.Age += 1;

Address address = (Address)repository.GetById(typeof(Address), "New York");
person.Address = address;

repository.Save(person);

The above example would save to the actual database via NHibernate. We can also implement the test repository to simulate saving to the database (for unit testing logic without affecting the database), as follows:

1
2
3
4
5
6
7
8
9
10
11
// Using a test repository.
IRepository repository = new RepositoryTest();

Person person = (Person)repository.GetById(typeof(Person), 12);
person.Age += 1;

Address address = (Address)repository.GetById(typeof(Address), "New York");
person.Address = address;

repository.Save(person);

The only required change in these two examples is the instantiation of RepositoryBase() or RepositoryTest(). You can find more detail about implementing multiple repository patterns and advanced techniques of implementing the concrete classes via reflection in the article Implementing the Repository Pattern.

The Business Object Model Revealed

Since we’ve discussed the NHibernate business object model and compared it to the repository pattern, it would only be fair to provide an implementation as well. The business object model allows calling each of the NHibernate method functions directly on the DTO objects. We can implement a start of the business object model as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using NHibernate;

public abstract class BusinessBaseType<TEntity>
where TEntity : class, new()
{
protected BusinessBaseType()
{
}

public TEntity Load(object pk)
{
using (ISession session = Database.OpenSession())
{
return (TEntity)session.Load(typeof(TEntity), pk);
}
}

public void Delete(object pk)
{
using (ISession session = Database.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete(pk);
transaction.Commit();
}
}
}

public void Save(object obj)
{
using (ISession session = Database.OpenSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
session.SaveOrUpdate(obj);
transaction.Commit();
}
}
}

public List<TEntity> ToList()
{
List<TEntity> resultList = new List<TEntity>();

using (ISession session = Database.OpenSession())
{
var objects = session
.CreateCriteria(typeof(TEntity))
.List();

foreach (object obj in objects)
{
resultList.Add((TEntity)obj);
}
}

return resultList;
}
}

Note in the above code, we’ve defined a C# generic business base class, which all NHibernate DTO classes would inherit from, providing the data access methods to each type. The methods of the business base class are also defined as generic in order to work with any DTO type. While sessions and transactions are not sharable between classes by the client, this business class could be extended to provide this functionality.

To utilize the business object model, you would define your NHibernate DTO types to inherit from the base class as follows:

1
2
3
4
5
6
7
8
9
10
11
12
public class Person : BusinessBaseType<Person>
{
public int PersonId { get; set; }
public string Name { get; set; }

...

// Create additional helper functions for Save(), // Delete(), Load() to pass "this" to the business base class.

...
}

Conclusion

NHibernate provides a complete set of data access methods for working with database functionality in C# ASP .NET web applications. The flexibility of the repository pattern, when used with NHibernate, provides a clean data access layer for integration in applications, with the ability to support transaction and database session management. By implementing a repository pattern or business object model in conjunction with NHibernate DTO types, developers can help create more robust and maintainable web applications.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share