I know that there allready are implementations for NHibernate-based repositories (eg. RhinoTools) but I can’t help to fiddling a bit with a design that I feel comfortable with. When building business applications I like to use a domain model (Read more – Martin Fowler) and to use repositories (Read more – Martin Fowler) for storing my domain objects. When designing we storage-layer (data-access-layer) I always define each repository using an Interface. This is something I do to be able to create fakes and mocks of my repositories; as well as create different implementations of them, e.g. one using NHibernate and another using SubSonic etc. Each implementation has it’s base-classes etc. just to get rid of repetitive boilerplate coding. For my NHibernate base implementation it looks something like this:
NHibContext (see earlier post) is responsible for creating NHibSessions (which is a wrapper around an NHibernate.ISession). The NHibContext is injected in my NHibRepository so that it can create a NHibSession for use within the repository (this is probably something that might get changed and instead a NHibSession will be injected directly). Than I create e.g. a NHibUserAccountRepository that extends the NHibRepository and also implements the IUserAccountRepository. Lets pick it apart.
Why IRepository?
I want a common repository interface independent of which underlying storage framework is used or what domain object I’m dealing with.
public interface IRepository<T> where T : class, IEntity
{
void Commit();
void Rollback();
void Insert(T item);
void Update(T item);
void Delete(T item);
T SingleBy(Expression<Func<T, bool>> query);
IList<T> List();
IList<T> ListBy(Expression<Func<T, bool>> query);
}
Why IUserAccountRepository?
As I said before, I want to be able to create fakes and mocks of my repositories and I want to be able to use polymorphism when dealing with my domain-object-specific repositories, so that I can work against an interface, independent of the actual implementation.
public interface IUserAccountRepository : IRepository<UserAccount>
{
}
Why NHibRepository
I want a strongly typed repository-base that keeps me from writing boilerplate code in all my specific NHibernate-based repositories.
public abstract class NHibRepository<T> : IRepository<T>, IDisposable
where T : class, IEntity
{
protected NHibContext Context { get; private set; }
protected NHibSession Session { get; private set; }
protected NHibRepository(NHibContext context)
{
Context = context;
Session = Context.CreateNewSession();
}
public void Dispose()
{
Disposer.TryDispose(Session);
}
public void Commit()
{
Session.Commit();
}
public void Rollback()
{
Session.Rollback();
}
public virtual void Insert(T item)
{
Session.Insert<T>(item);
}
public virtual void Update(T item)
{
Session.Update<T>(item);
}
public virtual void Delete(T item)
{
Session.Delete<T>(item);
}
public virtual T SingleBy(Expression<Func<T, bool>> query)
{
return Session.GetItemBy<T>(query);
}
public virtual IList<T> List()
{
return Session.GetList<T>();
}
public virtual IList<T> ListBy(Expression<Func<T, bool>> query)
{
return Session.GetListBy<T>(query);
}
}
UserAccountRepository against NHibernate
public class UserAccountRepository : NHibRepository<UserAccount>, IUserAccountRepository
{
public UserAccountRepository(NHibContext context)
: base(context)
{}
}
Why NHibSession?
Simple. I want a slimmer and unified interface for interacting with the NHibernate-core and I want it to take care of transactions.
public class NHibSession
: IDisposable
{
private ISession InnerSession { get; set; }
private ITransaction Transaction { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="NHibSession"/> class.
/// </summary>
/// <param name="session">The session.</param>
public NHibSession(ISession session)
{
InnerSession = session;
InnerSession.FlushMode = FlushMode.Commit;
SetupNewTransaction();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (Transaction != null)
{
Transaction.Rollback();
Transaction.Dispose();
Transaction = null;
}
if (InnerSession != null)
{
InnerSession.Dispose(); //InnerSession.Close(); Do not use! It breaks when outer transactionscopes are active! Should only use Dispose.
InnerSession = null;
}
}
}
~NHibSession()
{
Dispose(false);
}
public void Commit()
{
Transaction.Commit();
SetupNewTransaction();
}
public void Rollback()
{
Transaction.Rollback();
SetupNewTransaction();
}
private void SetupNewTransaction()
{
if (Transaction != null)
Transaction.Dispose();
Transaction = InnerSession.BeginTransaction();
}
/// <summary>
/// Creates and returns a Query.
/// </summary>
/// <param name="queryString">The query string.</param>
/// <returns></returns>
public IQuery CreateQuery(string queryString)
{
return InnerSession.CreateQuery(queryString);
}
/// <summary>
/// Creates and returns a Criteria.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public ICriteria CreateCriteria<T>()
where T : class
{
return InnerSession.CreateCriteria<T>();
}
/// <summary>
/// Gets an item that matches sent expression.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
public T GetItemBy<T>(Expression<Func<T, bool>> query)
{
return InnerSession.Linq<T>().SingleOrDefault(query);
}
/// <summary>
/// Returns item via Id.
/// </summary>
/// <typeparam name="TReturn"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public TReturn GetItemById<TReturn, TId>(TId id)
{
return InnerSession.Get<TReturn>(id);
}
/// <summary>
/// Returns item via NHibernate Criterions.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="criterions"></param>
/// <returns></returns>
public T GetItemByCriterions<T>(params ICriterion[] criterions)
{
return AddCriterions(InnerSession.CreateCriteria(typeof(T)), criterions).UniqueResult<T>();
}
/// <summary>
/// Returns a list of ALL items.
/// </summary>
/// <remarks>ALL items are returned.</remarks>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IList<T> GetList<T>()
{
return GetListByCriterions<T>(null);
}
/// <summary>
/// Returns a list of items matching sent expression.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="query">The query.</param>
/// <returns></returns>
public IList<T> GetListBy<T>(Expression<Func<T, bool>> query = null)
{
return InnerSession.Linq<T>().Where(query).ToList();
}
/// <summary>
/// Returns list of item matching sent criterions.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="criterions"></param>
/// <returns></returns>
public IList<T> GetListByCriterions<T>(params ICriterion[] criterions)
{
ICriteria criteria = AddCriterions(InnerSession.CreateCriteria(typeof(T)), criterions);
IList<T> result = criteria.List<T>();
return result ?? new List<T>(0);
}
/// <summary>
/// Deletes sent item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void Delete<T>(T obj)
{
InnerSession.Delete(obj);
}
/// <summary>
/// Deletes sent item by id.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <param name="id"></param>
public void DeleteById<T, TId>(TId id)
{
Delete(GetItemById<T, TId>(id));
}
/// <summary>
/// Deletes by query.
/// </summary>
/// <param name="query"></param>
public void DeleteByQuery(string query)
{
InnerSession.Delete(query);
}
/// <summary>
/// Inserts sent item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void Insert<T>(T obj)
{
InnerSession.Save(obj);
}
/// <summary>
/// Updates sent item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void Update<T>(T obj)
{
InnerSession.Update(obj);
}
private static ICriteria AddCriterions(ICriteria criteria, ICriterion[] criterions)
{
if (criterions != null)
for (int c = 0; c < criterions.Length; c++)
criteria = criteria.Add(criterions[c]);
return criteria;
}
}
Consume it
To show you how I consume it, let me show you a simple test.
[TestMethod]
public void CanInsertSingleUserAccount()
{
var newUserAccount = CreateUserAccount();
IUserAccountRepository userRepository = CreateRepository();
userRepository.Insert(newUserAccount);
userRepository.Commit();
Assert.AreEqual(1, StorageTestHelper.RowCount(UserAccountsTableName));
}
private static IUserAccountRepository CreateRepository()
{
return new UserAccountRepository(StorageTestHelper.NHibContext);
}
Thats it. Hope you find it inspiring.
//Daniel