Implementing UnitOfWork with Castle.Windsor

2019-02-01 07:17发布

问题:

Simple question.

How do I use UnitOfWork with Castle.Windsor, nHibernate, and ASP.NET MVC?

Now for the extended details. In my quest to understand the UnitOfWork pattern, I'm having difficulty coming across anything that uses a direct example in conjunction with Castle.Windsor, specifically in regards to the way it needs to be installed.

Here is my understanding so far.

IUnitOfWork

  • The IUnitOfWork interface is used to declare the pattern
  • The UnitOfWork class must Commit and Rollback transactions, and Expose a Session.

So with that said, here is my IUnitOfWork. (I am using Fluent nHibernate)

public interface IUnitOfWork : IDisposable
{
    ISession Session { get; private set; }
    void Rollback();
    void Commit();
}

So here is my Castle.Windsor Container Bootstrapper (ASP.NET MVC)

public class WindsorContainerFactory
{
    private static Castle.Windsor.IWindsorContainer container;
    private static readonly object SyncObject = new object();

    public static Castle.Windsor.IWindsorContainer Current()
    {
        if (container == null)
        {
            lock (SyncObject)
            {
                if (container == null)
                {
                    container = new Castle.Windsor.WindsorContainer();

                    container.Install(new Installers.SessionInstaller());
                    container.Install(new Installers.RepositoryInstaller());
                    container.Install(new Installers.ProviderInstaller());
                    container.Install(new Installers.ControllerInstaller());
                }
            }

        }

        return container;
    }
}

So now, in my Global.asax file, I have the following...

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        // Register the Windsor Container
        ControllerBuilder.Current
            .SetControllerFactory(new Containers.WindsorControllerFactory());
    }

Repository

Now I understand that I need to pass the ISession to my Repository. So then, let me assume IMembershipRepository.

class MembershipRepository : IMembershipRepository
{
   private readonly ISession session;
   public MembershipRepository(ISession session)
   {
      this.session = session;
   }

   public Member RetrieveMember(string email)
   {
      return session.Query<Member>().SingleOrDefault( i => i.Email == email );
   }
}

So I am confused, now. Using this method, the ISession doesn't get destroyed properly, and the UnitOfWork never gets used.

I've been informed that UnitOfWork needs to go in the Web Request Level - but I cannot find anything explaining how to actually go about this. I do not use a ServiceLocator of any sort ( as when I tried, I was told this was also bad practice... ).

Confusion -- How does a UnitOfWork get created?

I just don't understand this, in general. My thought was that I would start passing UnitOfWork into the Repository constructors - but if it has to go in the Web Request, I'm not understanding where the two relate.

Further Code

This is extra code for clarification, simply because I seem to have a habit of never providing the right information for my questions.

Installers

public class ControllerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            AllTypes.FromThisAssembly()
            .BasedOn<IController>()
            .Configure(c => c.LifeStyle.Transient));
    }
}

public class ProviderInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component
            .For<Membership.IFormsAuthenticationProvider>()
            .ImplementedBy<Membership.FormsAuthenticationProvider>()
            .LifeStyle.Singleton
        );
    }
}

public class RepositoryInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component
            .For<Membership.IMembershipRepository>()
            .ImplementedBy<Membership.MembershipRepository>()
            .LifeStyle.Transient
        );

        container.Register(
            Component
            .For<Characters.ICharacterRepository>()
            .ImplementedBy<Characters.CharacterRepository>()
            .LifeStyle.Transient
        );
    }
}

public class SessionInstaller : Castle.MicroKernel.Registration.IWindsorInstaller
{
    private static ISessionFactory factory;
    private static readonly object SyncObject = new object();

    public void Install(Castle.Windsor.IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<ISessionFactory>()
                .UsingFactoryMethod(SessionFactoryFactory)
                .LifeStyle.Singleton
            );

        container.Register(
            Component.For<ISession>()
            .UsingFactoryMethod(c => SessionFactoryFactory().OpenSession())
            .LifeStyle.Transient
        );
    }

    private static ISessionFactory SessionFactoryFactory()
    {
        if (factory == null)
            lock (SyncObject)
                if (factory == null)
                    factory = Persistence.SessionFactory.Map(System.Web.Configuration.WebConfigurationManager.ConnectionStrings["Remote"].ConnectionString);
        return factory;
    }
}

UnitOfWork

Here is my UnitOfWork class verbatim.

public class UnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory sessionFactory;
    private readonly ITransaction transaction;

    public UnitOfWork(ISessionFactory sessionFactory)
    {
        this.sessionFactory = sessionFactory;
        Session = this.sessionFactory.OpenSession();
        transaction = Session.BeginTransaction();
    }

    public ISession Session { get; private set; }

    public void Dispose()
    {
        Session.Close();
        Session = null;
    }

    public void Rollback()
    {
        if (transaction.IsActive)
            transaction.Rollback();
    }

    public void Commit()
    {
        if (transaction.IsActive)
            transaction.Commit();
    }
}

回答1:

Your NH Session is a Unit of Work already http://nhforge.org/wikis/patternsandpractices/nhibernate-and-the-unit-of-work-pattern.aspx

So I'm not sure why you would ever need to abstract this out any further. (if anyone reading this answer know's why I would be happy to hear, I've honestly never heard of any reason why you would need to...)

I would implement a simple Session Per Request. I don't know how you would do that with Windsor since I've never used it, but with It's rather simple with StructureMap.

I wrap the structuremap factory to hold my session factory and inject the session into the repositories as required.

    public static class IoC
    {
        static IoC()
        {
            ObjectFactory.Initialize(x =>
            {
                x.UseDefaultStructureMapConfigFile = false;

                // NHibernate ISessionFactory
                x.ForSingletonOf<ISessionFactory>()
                 .Use(new SessionFactoryManager().CreateSessionFactory());

                // NHibernate ISession
                x.For().HybridHttpOrThreadLocalScoped()
                 .Use(s => s.GetInstance<ISessionFactory>().OpenSession());

                x.Scan(s => s.AssembliesFromApplicationBaseDirectory());
            });

            ObjectFactory.AssertConfigurationIsValid();
        }

        public static T Resolve<T>()
        {
            return ObjectFactory.GetInstance<T>();
        }

        public static void ReleaseAndDisposeAllHttpScopedObjects()
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        }
    }

In the global.asax file on Request_End I call the ReleaseAndDisposeAllHttpScopedObjects() method.

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            IoC.ReleaseAndDisposeAllHttpScopedObjects();
        }

So the session is opened when I call my first repository, and when the request is ended it's disposed of. The repositories have a constructor which takes ISession and assigns it to a property. Then I just resolve the repo like:

var productRepository = IoC.Resolve<IProductRepository>();

Hope that helps. There are many other ways of doing it, this is what works for me.



回答2:

Is it a linguistic/impedence mismatch issue that the library terms don't jive with the lingo you are familiar with?

I am pretty new to this [fluent] nhibernate, too, so I am still trying to figure it out, but my take is this:

Normally, associate the ISession with an Application session (eg, if it were a web app, you would might consider associating the creation of the session with the Application_Start event, and dispose when the app shuts down -- gracefully or not). When the scope of the app goes away, so should the repository.

The UnitOfWork is just a way of wrapping/abstraction transactions, where you have more than one action to perform during an update, and to remain consistent they must both complete, in sequence, and each successfully. Such as when applying more than trivial business rules to data creation, analysis, or transforms...

Here is a link to a blog post that provides an example of using ISession and UnitOfWork in a fluent style. http://blog.bobcravens.com/2010/06/the-repository-pattern-with-linq-to-fluent-nhibernate-and-mysql/#comments

EDIT: Just to emphasize, I don't think you -must- use a unit of work for every operation against a repository. The UnitOfWork is only really needed when a transaction is the only reasonably choice, but I am just starting with this, too.