Managing RavenDb session in Windsor under NService

2020-07-13 14:09发布

问题:

I'm using NServiceBus (3.2.2), RavenDB (1.2.2017-Unstable) and Windsor (3.0.0.4001) in an MVC 4 project.

I have a IHandleMessages class that handles 3 different messages, and that needs an IDocumentSession, and therefore defines a property such as:

public IDocumentSession DocumentSession { get; set; }

I've copied the RavenDbUnitOfWork implementation from NServiceBus' website

I've registered IDocumentStore, IDocumentSession and IManageUnitsOfWork in my Windsor container as follow:

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 is configured to use my container:

Configure.With()
         .CastleWindsorBuilder(container);

I'm encountering the problem that the UnitOfWork and the message handler receive different instances of the DocumentSession. This means that objects stored in the session in the message handler are not saved, since SaveChanges() is called on a different DocumentSession.

Removing the Transient lifestyle causes different kind of problems, that result in concurrency/conflicts when updating objects from RavenDb, since (probably) the message handler keeps getting the same instance of the DocumentSession, which holds a cached version of the updated object.

Update:

As suggested, I've tried changing the registration of the IDocumentSession in Windsor, to the Scope lifestyle, like this:

Component
    .For<IDocumentSession>()
    .UsingFactoryMethod(k => k.Resolve<IDocumentStore>().OpenSession())
    .LifestyleScope()

This causes exceptions when the container tries to resolve the MVC Controller, saying that the scope was not found, and asking if I forgot to call BeginScope().

回答1:

You need to have a scope of Per Message, not transient or singleton.



回答2:

I am assuming that your mvc controller has a direct dependency on the IDocumentStore. You need to call container.BeginScope() before each request from the web. You can either do this as an action filter attribute http://msdn.microsoft.com/en-us/library/system.web.mvc.actionfilterattribute.aspx or as an AOP aspect on the controller itself http://cangencer.wordpress.com/2011/06/02/asp-net-mvc-3-aspect-oriented-programming-with-castle-interceptors/.



回答3:

The issue is you need different lifestyles when using nservicebus in an asp.net mvc website when sharing the IDocumentSession in the same container.

For ASP.NET MVC you need a PerWebRequest lifestyle and for NServiceBus you need the Scoped lifestyle.

To do that i've used the hybrid lifestyle code in the castle contrib project: https://github.com/castleprojectcontrib/Castle.Windsor.Lifestyles/tree/master/Castle.Windsor.Lifestyles

When calling from an ASP.NET context, it uses the WebRequestScopeAccessor. For NServicebus you need the LifetimeScopeAccessor. This is not in the contrib project, but is easy to add:

public class HybridPerWebRequestLifetimeScopeScopeAccessor : HybridPerWebRequestScopeAccessor
{
    public HybridPerWebRequestLifetimeScopeScopeAccessor()
        : base(new LifetimeScopeAccessor())
    {
    }
}

And in your registration code you need something like:

container.Register(Component.For<IDocumentSession>().LifestyleScoped<HybridPerWebRequestLifetimeScopeScopeAccessor>().UsingFactoryMethod(() => RavenDbManager.DocumentStore.OpenSession()));

And here's an implementation for Rhino Service Bus i used before switching to nservicebus:

https://gist.github.com/4655544