У меня есть постоянный фреймворк, построенный поверх NHibernate, который используется в нескольких веб-приложениях.Он скрывает реализацию NH за интерфейсом IRepository
и IRepository<T>
с конкретными экземплярами, предоставляемыми Unity (таким образом, я теоретически мог бы довольно просто заменить NHibernate, скажем, на Entity Framework).
Так как Unity не поддерживает (или, по крайней мере, версию, которую я использую), не поддерживает передачу параметров конструктора, кроме тех, которые сами являются инъекциями зависимостей, передача в существующую NH ISession не являетсявозможный;но я хочу, чтобы все объекты в UOW использовали один и тот же ISession.
Я решаю эту проблему, имея управляющий класс репозитория, который управляет доступом к ISession для каждого потока:
public static ISession Session
{
get
{
lock (_lockObject)
{
// if a cached session exists, we'll use it
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
{
return (ISession)PersistenceFrameworkContext.Current.Items[NHibernateRepository.SESSION_KEY];
}
else
{
// must create a new session - note we're not caching the new session here... that's the job of
// BeginUnitOfWork().
return _factory.OpenSession(new NHibernateInterceptor());
}
}
}
}
В этом примере PersistenceFrameworkContext.Current.Items
обращается к IList<object>
, которыйхранится либо ThreadStatic
, если не в веб-контексте, либо в HttpContext.Current.Items
, если он находится в веб-контексте (во избежание проблем с пулом потоков).Первый вызов свойства создает экземпляр ISession
из сохраненного экземпляра фабрики, последующие вызовы просто извлекают его из хранилища.Блокировка немного замедлит работу, но не так сильно, как простая блокировка статического экземпляра ISession
в области приложения.
Затем у меня есть методы BeginUnitOfWork
и EndUnitOfWork
, чтобы позаботиться о UOW - у меня естьспециально запрещены вложенные UOW, потому что, честно говоря, ими было трудно управлять.
public void BeginUnitOfWork()
{
lock (_lockObject)
{
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
EndUnitOfWork();
ISession session = Session;
PersistenceFrameworkContext.Current.Items.Add(SESSION_KEY, session);
}
}
public void EndUnitOfWork()
{
lock (_lockObject)
{
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
{
ISession session = (ISession)PersistenceFrameworkContext.Current.Items[SESSION_KEY];
PersistenceFrameworkContext.Current.Items.Remove(SESSION_KEY);
session.Flush();
session.Dispose();
}
}
}
Наконец, пара методов обеспечивает доступ к репозиториям, специфичным для типа домена:
public IRepository<T> For<T>()
where T : PersistentObject<T>
{
return Container.Resolve<IRepository<T>>();
}
public TRepository For<T, TRepository>()
where T : PersistentObject<T>
where TRepository : IRepository<T>
{
return Container.Resolve<TRepository>();
}
(Здесь, PersistentObject<T>
- это базовый класс, обеспечивающий поддержку идентификаторов и равных.)
Таким образом, доступ к данному репозиторию осуществляется по шаблону
NHibernateRepository.For<MyDomainType>().Save();
. Затем он отображается так, что вы можете использовать
MyDomainType.Repository.Save();
Если у заданного типа есть специализированный репозиторий (т.е. ему нужно больше, чем он может получить от IRepository<T>
), тогда я создаю интерфейс, происходящий от IRepository<T>
, расширяющей реализации, наследующей от моего IRepository<T>
реализации, и в самом типе домена я переопределяю статическое свойство Repository
, используя new
new public static IUserRepository Repository
{
get
{
return MyApplication.Repository.For<User, IUserRepository>();
}
}
(MyApplication
[который в реальном продукте называется чем-то менее хитрым) - этокласс фасадов, который заботится о предоставлении экземпляра Repository
через Unity, чтобы у вас не было зависимости от конкретной реализации репозитория NHibernate в классах вашего домена.)
Это дает мне полную возможность подключения через Unity для реализации репозитория, легкодоступ к хранилищу в коде без скачков и прозрачного управления для каждого потока ISession
.
Там намного больше кода, чем просто выше (и я значительно упростил пример кода),но у вас есть общее представление.
MyApplication.Repository.BeginUnitOfWork();
User user = User.Repository.FindByEmail("wibble@wobble.com");
user.FirstName = "Joe"; // change something
user.LastName = "Bloggs";
// you *can* call User.Repository.Save(user), but you don't need to, because...
MyApplication.Repository.EndUnitOfWork();
// ...causes session flush which saves the changes automatically
В моем веб-приложении у меня есть сеанс для запроса, поэтому BeginUnitOfWork
и EndUnitOfWork
вызывают в BeginRequest
и EndRequest
соответственно.