Searching on Stack Overflow and Google, I find many and many different implementation for Unit-of-Work/Repository patterns, but none of that really convinced me; each had some small defect that prevented me from using it. So I tried to create my own version and would like to know what you think.
Essentially everything is based on the use of interfaces and UnitOfWork pattern built in the style of the Entity Framework, even if I use NHibernate as implementation.
Wanting to have absolutely generic interfaces and can therefore accommodate both Entity Framework and NHibernate, my UoW does not expose any of the specific concepts of the two repositories such as session or transaction.
This is the usage:
using (IUnitOfWork uow = new NHUnitOfWork())
{
uow.StartOperation();
Class1 c1 = _uow.Class1Repo.Read<Class1>(idClasse1);
c1.Desc = "edited";
uow.Class1Repo.Update(c1);
}
Of course the new instance can be avoided using dependency injection.
These are the interfaces:
public interface IUnitOfWork : IDisposable{
void StartOperation();
void SaveAll();
void DiscardChanges();
void OpenConnection();
void CloseConenction();
IClass1Repository Class1Repo { get; }
// All other repository here
}
At this point I created the basic interface of my generic repository containing all the operations that must be provided (if you want you can create generic repository for distinct divide the reading operations from those of writing so that you can manage the repository with limited permissions for objects that not foresee the change, but I had no need of this type).
public interface IRepoBase<T> : IDisposable where T : class {
void Create(T obj);
T Read<T>(object id);
void Update(T obj);
void Delete(T obj);
}
All the remaining read operations of individual objects will be managed in their specific repository.
public interface IClass1Repository : IDisposable, IRepoBase<Class1>
{
IEnumerable<Class1> FindAll();
string GetDescription();
}
All the conditions and filters to queries will be made directly into the repository using more specific methods, why not use it IEnumerable
IQueryable
. In this way they have no ties to Linq.
public class NHUnitOfWork: IUnitOfWork{
private ITransaction _transaction;
private NHSessionFactory _nhHelper;
private ISession _session;
public IClass1Repository Class1Repo { get; private set; }
public NHUnitOfWork(NHSessionFactory nhHelper) {
_nhHelper = nhHelper ?? new NHSessionFactory();
InitializeSession(); }
public NHUnitOfWork() {
_nhHelper = new NHSessionFactory();
InitializeSession(); }
private void InitializeSession() {
if (_session == null)
_session = _nhHelper.OpenSession();
Class1Repo = new Class1Repository(this._session);
// All other repository here }
public void StartOperation() {
InitializeSession();
_transaction = _session.BeginTransaction(); }
public void SaveAll() {
if (_transaction != null) {
_transaction.Commit();
_transaction = null; } }
public void DiscardChanges() {
Contract.Ensures(_session.Transaction.IsActive == false);
if (_transaction != null) {
_transaction.Rollback();
_transaction = null; } }
public void OpenConnection() {
InitializeSession(); }
public void CloseConnection() {
if (_transaction != null) {
_transaction.Dispose();
_transaction = null; }
if (_session != null) {
_session.Dispose();
_session = null; }
}
// Disposable pattern here
}
OpenConnection
and CloseConenction
hide the NHibernate session, while StartOperation
, SaveAll
and DiscardChanges
hide the concept of atomic operation (transaction).
NHibernate implementation of BaseRepository
:
class NHRepoBase<T> : IDisposable, IRepoBase<T> where T : class
{
protected ISession _session;
public NHRepoBase(ISession session)
{_session = session; }
public void Create(T obj)
{ _session.SaveOrUpdate(obj); }
public void Update(T obj)
{ _session.Merge<T>(obj); }
public void Delete(T obj)
{ _session.Delete(obj); }
public T Read<T>(object id)
{ return _session.Get<T>(id); }
private bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (!this.disposed){
if (disposing)
_session.Dispose();
}
this.disposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
A simple specific repository:
class Class1Repository : NHRepoBase<Class1>, IClass1Repository, IDisposable {
public Class1Repository(ISession session) : base(session) { }
public IEnumerable<Class1> FindAll()
{ return _session.QueryOver<Class1>().List(); }
public string GetDescription()
{ return _session.QueryOver<Class1>().Select(c => c.Desc).SingleOrDefault<string>(); }
}
StartOperation
? Don't create objects in an invalid state. Call it from the constructor. \$\endgroup\$