Управление сессией RavenDb в Виндзоре под NServiceBus
Я использую NServiceBus (3.2.2), RavenDB (1.2.2017-Unstable) и Windsor (3.0.0.4001) в проекте MVC 4.
У меня есть класс IHandleMessages, который обрабатывает 3 разных сообщения, и для которого требуется IDocumentSession, и, следовательно, определяет такое свойство, как:
public IDocumentSession DocumentSession { get; set; }
Я скопировал реализацию RavenDbUnitOfWork с веб- сайта NServiceBus
Я зарегистрировал IDocumentStore, IDocumentSession и IManageUnitsOfWork в своем контейнере Windsor следующим образом:
container.Register(
Component
.For<IManageUnitsOfWork>()
.ImplementedBy<RavenUnitOfWork>()
.LifestyleTransient()
);
container.Register(
Component
.For<IDocumentStore>()
.UsingFactoryMethod(k => DocumentStoreHolder.DocumentStore)
.LifestyleSingleton(),
Component
.For<IDocumentSession>()
.UsingFactoryMethod(k => k.Resolve<IDocumentStore>().OpenSession())
.LifestyleTransient()
);
NServiceBus настроен на использование моего контейнера:
Configure.With()
.CastleWindsorBuilder(container);
Я сталкиваюсь с проблемой, что UnitOfWork и обработчик сообщений получают разные экземпляры DocumentSession. Это означает, что объекты, хранящиеся в сеансе в обработчике сообщений, не сохраняются, так как SaveChanges() вызывается в другом DocumentSession.
Удаление временного образа жизни вызывает проблемы различного рода, которые приводят к параллелизму / конфликтам при обновлении объектов из RavenDb, поскольку (вероятно) обработчик сообщений продолжает получать один и тот же экземпляр DocumentSession, который содержит кэшированную версию обновленного объекта.
Обновить:
Как и предполагалось, я попытался изменить регистрацию IDocumentSession в Виндзоре на стиль жизни Scope, например:
Component
.For<IDocumentSession>()
.UsingFactoryMethod(k => k.Resolve<IDocumentStore>().OpenSession())
.LifestyleScope()
Это вызывает исключения, когда контейнер пытается разрешить контроллер MVC, говоря, что область не найдена, и спрашивая, не забыл ли я вызвать BeginScope().
3 ответа
У вас должна быть область действия на сообщение, а не временная или единичная.
Я предполагаю, что ваш контроллер MVC имеет прямую зависимость от IDocumentStore. Вам необходимо вызывать container.BeginScope() перед каждым запросом из Интернета. Вы можете сделать это как атрибут фильтра действий http://msdn.microsoft.com/en-us/library/system.web.mvc.actionfilterattribute.aspx или как аспект AOP на самом контроллере http://cangencer.wordpress.com/2011/06/02/asp-net-mvc-3-aspect-oriented-programming-with-castle-interceptors/.
Проблема в том, что вам нужен другой образ жизни при использовании nservicebus на веб-сайте asp.net mvc при совместном использовании IDocumentSession в том же контейнере.
Для ASP.NET MVC вам нужен стиль жизни PerWebRequest, а для NServiceBus вам нужен стиль жизни Scoped.
Чтобы сделать это, я использовал гибридный код образа жизни в проекте содействия замку: https://github.com/castleprojectcontrib/Castle.Windsor.Lifestyles/tree/master/Castle.Windsor.Lifestyles
При вызове из контекста ASP.NET он использует WebRequestScopeAccessor. Для NServicebus вам нужен LifetimeScopeAccessor. Этого нет в проекте contrib, но его легко добавить:
public class HybridPerWebRequestLifetimeScopeScopeAccessor : HybridPerWebRequestScopeAccessor
{
public HybridPerWebRequestLifetimeScopeScopeAccessor()
: base(new LifetimeScopeAccessor())
{
}
}
И в вашем регистрационном коде вам нужно что-то вроде:
container.Register(Component.For<IDocumentSession>().LifestyleScoped<HybridPerWebRequestLifetimeScopeScopeAccessor>().UsingFactoryMethod(() => RavenDbManager.DocumentStore.OpenSession()));
А вот реализация Rhino Service Bus, которую я использовал перед переключением на nservicebus: