понедельник, 17 декабря 2012 г.

Unit Of Work + NHibernate Explained

Мой вариант реализации Unit Of Work, применительно к NHibernate.

1. Интерфейс IUnitOfWork:

 public interface IUnitOfWork : IServiceProviderIDisposable
 {
     void Commit();
 }

  • Лишь один метод Commit, больше ничего не нужно. Добавление метода Rollback может привести к тому, что в результате ошибки мы можем попасть в третье состояние, когда ни Commit ни Rollback не были вызваны.
  • Никаких методов Add/Remove/Save/Get etc. Это не является ответственностью UoW.
  • Наследование от IServiceProvider/IDisposable нужно лишь в случае, если не используется IoC контейнер с поддержкой nested lifetime scope.
2. Интерфейс IServiceProvider:

 public interface IServiceProvider
 {
     IRepository GetRepository();
 }

  • Как я уже сказал он нужен только в случае отсутствия нормального IoC контейнера.
  • Нужен для получения scoped сервисов, чье время жизни будет связано с UoW.
  • По хорошему метод должен быть таким:
       T GetService();
    
         но я решил упростить пример :)

3. Интерфейс IRepository:

 public interface IRepository
 {
     T Get(int id);
 
     void Add(T obj);
 
     void Delete(T obj);
 }

  • Вот он то и отвечает за все получение/сохранение объектов
  • Бросается в глаза отсутствие метода Save :)
4. Пример использования:

 var uowFactory = new UnitOfWorkFactory();
 
 int catId;
 using (var uow = uowFactory.CreateUnitOfWork())
 {
     var repository = uow.GetRepository<Cat>();
     var cat = new Cat { Name = "Chalky", Age = 3 };
     repository.Add(cat);
 
     uow.Commit();
 
     catId = cat.Id;
 }
 
 using (var uow = uowFactory.CreateUnitOfWork())
 {
     var repository = uow.GetRepository<Cat>();
     var cat = repository.Get(catId);
     cat.Age = 4;
 
     uow.Commit();
 }
5. Реализация:

 internal sealed class UnitOfWork : IUnitOfWork
 {
     private readonly ISession _session;
     private readonly ITransaction _transaction;
 
     public UnitOfWork(ISession session)
     {
         _session = session;
         _transaction = session.BeginTransaction();
     }
 
     public void Commit()
     {
         if (!_session.IsOpen)
         {
             throw new InvalidOperationException("Session is already closed!");
         }
 
         _transaction.Commit();
         _session.Close();
     }
 
     public IRepository GetRepository()
     {
         return new Repository(_session);
     }
 
     public void Dispose()
     {
         try
         {
             _session.Dispose();
         }
         catch
         {
              // TODO: log exception
         }
     }
 }


Если есть желание сэкономить на спичках, то можно задуматься о lazy создании транзакции, чтобы UoW с no-op не приводил к открытию соединения.


 internal sealed class Repository : IRepository
 {
     private readonly ISession _session;
 
     public Repository(ISession session)
     {
         _session = session;
     }
 
     public T Get(int id)
     {
         return _session.Get(id);
     }
 
     public void Add(T obj)
     {
         _session.Save(obj);
     }
 
     public void Delete(T obj)
     {
         _session.Delete(obj);
     }
 }
 
 public sealed class UnitOfWorkFactory
 {
     private readonly ISessionFactory _sessionFactory;
 
     public UnitOfWorkFactory()
     {
         // TODO:
         //_sessionFactory = configuration.BuildSessionFactory();
     }
 
     public IUnitOfWork CreateUnitOfWork()
     {
         return new UnitOfWork(_sessionFactory.OpenSession());
     }
 }
 
 public sealed class Cat
 {
     public int Id { getset; }
 
     public string Name { getset; }
 
     public int Age { getset; }
 }

Wider Two Column Modification courtesy of The Blogger Guide