我在我们的解决方案中使用NServiceBus v4.3,MVC4,RavenDB 2.5和StructureMap 2.6.4。
我在StructureMap下遇到的问题与this question的responses中描述的问题类似,我需要不同的生命周期,MVC Controller和NServiceBus Handler在我的Web项目中使用RavenDB的IDocumentSession。
特别是在我的情况下,如果我使用HybridHttpOrThreadLocalScoped(如上面对Windsor的回答建议)生命周期,会话没有被正确处理,我很快就达到了30个事务限制错误。如果我使用HttpContext生命周期,则不会调用Web项目中的NSB事件处理程序。
在我的控制器中,会话被包含在通过MVC ActionFilter应用的工作单元中。我也使用处理程序中的UoW,因为我的注册表已连线以从UoW检索会话。代码是这样的:
RavenDbWebRegistry.cs
public sealed class RavenDbWebRegistry : Registry
{
public RavenDbWebRegistry()
{
// register RavenDB document store
ForSingletonOf<IDocumentStore>().Use(() =>
{
var documentStore = new DocumentStore
{
ConnectionStringName = "RavenDB",
Conventions =
{
IdentityPartsSeparator = "-",
JsonContractResolver = new PrivatePropertySetterResolver(),
},
};
documentStore.Initialize();
return documentStore;
});
For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Add(ctx =>
{
var uow = (IRavenDbUnitOfWork)ctx.GetInstance<IUnitOfWork>();
return uow.DocumentSession;
});
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<WebRavenDbUnitOfWork>();
}
}
Web项目处理程序示例:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public void Handle(ISiteCreatedEvent message)
{
try
{
Debug.Print(@"{0}{1}", message, Environment.NewLine);
Uow.Begin();
var site = DocumentSession.Load<Site>(message.SiteId);
Uow.Commit();
//invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
//TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
Uow.Rollback();
}
}
}
ActionFilter的用法:
[RavenDbUnitOfWork]
public ViewResult CreateNew(int? id)
{
if (!id.HasValue || id.Value <= 0)
return View(new SiteViewModel { Guid = Guid.NewGuid() });
var targetSiteVm = MapSiteToSiteViewModel(SiteList(false)).FirstOrDefault(s => s.SiteId == id.Value);
return View(targetSiteVm);
}
WebRegistry(在我的MVC项目中设置NSB)
public sealed class WebRegistry : Registry
{
public WebRegistry()
{
Scan(x =>
{
x.TheCallingAssembly();
x.Assembly("IS.CommonLibrary.ApplicationServices");
x.LookForRegistries();
});
IncludeRegistry<RavenDbWebRegistry>();
FillAllPropertiesOfType<IUnitOfWork>();
FillAllPropertiesOfType<IDocumentSession>();
FillAllPropertiesOfType<StatusConversionService>();
FillAllPropertiesOfType<IStateRepository<TieState>>();
FillAllPropertiesOfType<IStateRepository<DedState>>();
FillAllPropertiesOfType<ITieService>();
FillAllPropertiesOfType<IDedService>();
FillAllPropertiesOfType<IHbwdService>();
//NServiceBus
ForSingletonOf<IBus>().Use(
NServiceBus.Configure.With()
.StructureMapBuilder()
.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.EndsWith("Command"))
.DefiningEventsAs(t => t.Namespace != null && t.Namespace.EndsWith("Event"))
.DefiningMessagesAs(t => t.Namespace == "Messages")
.RavenPersistence("RavenDB")
.UseTransport<ActiveMQ>()
.DefineEndpointName("IS.Argus.Web")
.PurgeOnStartup(true)
.UnicastBus()
.CreateBus()
.Start(() => NServiceBus.Configure.Instance
.ForInstallationOn<Windows>()
.Install())
);
//Web
For<HttpContextBase>().Use(() => HttpContext.Current == null ? null : new HttpContextWrapper(HttpContext.Current));
For<ModelBinderMappingDictionary>().Use(GetModelBinders());
For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();
For<IFilterProvider>().Use<StructureMapFilterProvider>();
For<StatusConversionService>().Use<StatusConversionService>();
For<ITieService>().Use<TieService>();
For<IDedService>().Use<DedService>();
For<IHbwdService>().Use<HbwdService>();
For<ISiteService>().Use<SiteService>();
IncludeRegistry<RedisRegistry>();
}
我尝试使用我能想到的每种可能的组合来配置我的注册表。
鉴于StructureMap混合生命周期不能像我期望的那样工作,我该怎么做才能实现正确的行为?
UoW对RavenDB是否必要/有益?我喜欢它(从我之前的NHibernate UoW ActionFilter中调整过它),因为它管理Controller Actions中会话的生命周期,但我对其他方法持开放态度。
我理想情况下是在Web项目中的一种方式 - 为控制器和处理程序分配完全不同的IDocumentSessions,但是无法以任何方式解决这个问题。
答案 0 :(得分:2)
首先,RavenDB已经通过包装IDocumentSession
来实现工作单元,因此不需要它。打开一个会话,调用SaveChanges()
并处理完成了工作单元
其次,可以通过几种方式为控制器实现会话。
一般指导是在Global.asax.cs
中设置商店。由于只有一个框架可以实现IDocumentSession
- RavenDB,因此您也可以从Global
实例化它。如果是存储库后面的NHibernate或Entity Framework,我明白了。但IDocumentSession是特定的RavenDB,所以请在Application_Start
中进行直接初始化。
public class Global : HttpApplication
{
public void Application_Start(object sender, EventArgs e)
{
// Usual MVC stuff
// This is your Registry equivalent, so insert it into your Registry file
ObjectFactory.Initialize(x=>
{
x.For<IDocumentStore>()
.Singleton()
.Use(new DocumentStore { /* params here */ }.Initialize());
}
public void Application_End(object sender, EventArgs e)
{
var store = ObjectFactory.GetInstance<IDocumentStore>();
if(store!=null)
store.Dispose();
}
}
在控制器中,添加基类,然后它可以为您打开和关闭会话。同样IDocumentSession
特定于RavenDB,因此依赖注入实际上对您没有帮助。
public abstract class ControllerBase : Controller
{
protected IDocumentSession Session { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext context)
{
Session = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
protected override void OnActionExecuted(ActionExecutedContext context)
{
if(this.IsChildAction)
return;
if(content.Exception != null && Session != null)
using(context)
Session.SaveChanges();
}
}
然后从那里继承基础控制器并从那里开始工作:
public class CustomerController : ControllerBase
{
public ActionResult Get(string id)
{
var customer = Session.Load<Customer>(id);
return View(customer);
}
public ActionResult Edit(Customer c)
{
Session.Store(c);
return RedirectToAction("Get", c.Id);
}
}
最后,我可以看到你正在使用StructureMap,所以只需要几个基本调用就可以从DI框架中获取Session:
public class SiteCreatedEventHandler : IHandleMessages<ISiteCreatedEvent>
{
public IBus Bus { get; set; }
public IUnitOfWork Uow { get; set; }
public IDocumentSession DocumentSession { get; set; }
public SiteCreatedEventHandler()
{
this.DocumentSession = ObjectFactory.GetInstance<IDocumentStore>().OpenSession();
}
public void Handle(ISiteCreatedEvent message)
{
using(DocumentSession)
{
try
{
Debug.Print(@"{0}{1}", message, Environment.NewLine);
///// Uow.Begin(); // Not needed for Load<T>
var site = DocumentSession.Load<Site>(message.SiteId);
//// Uow.Commit(); // Not needed for Load<T>
// invoke Hub and push update to screen
var context = GlobalHost.ConnectionManager.GetHubContext<AlarmAndNotifyHub>();
// TODO make sure this SignalR function is correct
context.Clients.All.displayNewSite(site, message.CommandId);
context.Clients.All.refreshSiteList();
}
catch (Exception ex)
{
//// Uow.Rollback(); // Not needed for Load<T>
}
}
}