Как управлять транзакциями с помощью ninject и Nhibernate с Isessionfactory, внедренными в репозиторий? - PullRequest
1 голос
/ 06 марта 2012

Я довольно новичок с паттернами DI и IoC.

public class LazySessionContext
{
    private readonly ISessionFactoryImplementor factory;
    private const string CurrentSessionContextKey = "NHibernateCurrentSession";

    public LazySessionContext(ISessionFactoryImplementor factory)
    {
        this.factory = factory;
    }

    /// <summary>
    /// Retrieve the current session for the session factory.
    /// </summary>
    /// <returns></returns>
    public ISession CurrentSession()
    {
        Lazy<ISession> initializer;
        var currentSessionFactoryMap = GetCurrentFactoryMap();
        if (currentSessionFactoryMap == null ||
            !currentSessionFactoryMap.TryGetValue(factory, out initializer))
        {
            return null;
        }
        return initializer.Value;
    }

    /// <summary>
    /// Bind a new sessionInitializer to the context of the sessionFactory.
    /// </summary>
    /// <param name="sessionInitializer"></param>
    /// <param name="sessionFactory"></param>
    public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        map[sessionFactory] = sessionInitializer;
    }

    /// <summary>
    /// Unbind the current session of the session factory.
    /// </summary>
    /// <param name="sessionFactory"></param>
    /// <returns></returns>
    public static ISession UnBind(ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        var sessionInitializer = map[sessionFactory];
        map[sessionFactory] = null;
        if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
        return sessionInitializer.Value;
    }

    /// <summary>
    /// Provides the CurrentMap of SessionFactories.
    /// If there is no map create/store and return a new one.
    /// </summary>
    /// <returns></returns>
    private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
    {
        var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<ISession>>)
                                HttpContext.Current.Items[CurrentSessionContextKey];
        if (currentFactoryMap == null)
        {
            currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
            HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
        }
        return currentFactoryMap;
    }
}

public interface ISessionFactoryProvider
{
    IEnumerable<ISessionFactory> GetSessionFactories();
}

public class SessionFactoryProvider
{
    public const string Key = "NHibernateSessionFactoryProvider";
}

public class NHibernateSessionModule : IHttpModule
{
    private HttpApplication app;

    public void Init(HttpApplication context)
    {
        app = context;
        context.BeginRequest += ContextBeginRequest;
        context.EndRequest += ContextEndRequest;
        context.Error += ContextError;
    }

    private void ContextBeginRequest(object sender, EventArgs e)
    {
        var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
        foreach (var sf in sfp.GetSessionFactories())
        {
            var localFactory = sf;
            LazySessionContext.Bind(
                new Lazy<ISession>(() => BeginSession(localFactory)),
                sf);
        }
    }

    private static ISession BeginSession(ISessionFactory sf)
    {
        var session = sf.OpenSession();
        session.BeginTransaction();
        return session;
    }

    private void ContextEndRequest(object sender, EventArgs e)
    {
        var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
        var sessionsToEnd = sfp.GetSessionFactories()
                               .Select(LazySessionContext.UnBind)
                               .Where(session => session != null);

        foreach (var session in sessionsToEnd)
        {
            EndSession(session);
        }
    }

    private void ContextError(object sender, EventArgs e)
    {
        var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
        var sessionstoAbort = sfp.GetSessionFactories()
                                .Select(LazySessionContext.UnBind)
                                .Where(session => session != null);

        foreach (var session in sessionstoAbort)
        {
            EndSession(session, true);
        }
    }

    private static void EndSession(ISession session, bool abort = false)
    {
        if (session.Transaction != null && session.Transaction.IsActive)
        {
            if (abort)
            {
                session.Transaction.Rollback();
            }
            else
            {
                session.Transaction.Commit();
            }
        }
        session.Dispose();
    }

    public void Dispose()
    {
        app.BeginRequest -= ContextBeginRequest;
        app.EndRequest -= ContextEndRequest;
        app.Error -= ContextError;
    }
}

Я получил этот образец от chinooknugets и jfmarillo от GitHub. Из приведенного выше кода я внедряю сессию в хранилище и управляю транзакцией через IHttpmodule. Теперь у меня есть две проблемы:

  1. Если я реализую управление транзакциями с помощью приведенного выше кода, он будет вызываться всякий раз, когда есть запрос, и он откроет сеанс для этого запроса. Это единственная цель реализации подхода «Session Per Request». Но что, если среди всех моих методов контроллера у меня есть только один метод, который на самом деле использует репозиторий, я не хочу открывать сеанс каждый раз, когда делаю запрос. Только во время действий, отмеченных атрибутом Transaction в контроллере, транзакция будет обработана.

  2. Я бы сделал запрос, и сессия будет открыта только тогда, когда репозиторий запросит его, так что это может быть реализовано через любой контейнер IoC.

  3. Я все еще хочу, чтобы события httpcontext обрабатывали транзакцию, чтобы контекст + = BegingRequest и контекст + = EndRequest. и транзакция будет обрабатываться внутри этого httpmodule с httpcontext по запросу. Но я не хочу реализовывать IhttpModule и помещать в web.config. Есть ли другая альтернатива для этого подхода?

  4. Открытие и закрытие сеанса будет осуществляться только внутри этих httpcontext, однако я хочу управлять им через контейнер IoC (предпочтительно, ninject), но только тогда, когда хранилище запрашивает этот сеанс. Следует помнить, что хранилище может быть инициализировано при вызове контроллера, но это не должно открывать сеанс внутри этого хранилища. Сеанс должен открываться, когда на самом деле репозиторий выполняет какие-либо временные действия.

Кто-нибудь объяснит, какой практике я должен следовать для этого сценария? Я использую Mvc 3 с ninject и nhibernate.

1 Ответ

5 голосов
/ 07 марта 2012

1, 2) Ninject позволяет создавать объекты Lazy с помощью Ninject.Extensions.Factory 3.0.0

class MyController
{
    public MyController(Lazy<SomeRepository> repository) { ... }
}

Таким образом, хранилище (и контекст) создается при использовании.

3) Почему вы хотите использовать HttpModule для этого?Есть гораздо более простые способы, например:

kernel.Bind<ISession>()
      .ToMethod(ctx => ctx.Kernel.Get<ISessionFactory().OpenSession())
      .InRequestScope()
      .OnActivation(session => OpenTransaction(session))
      .OnDeactivation(session => EndTransaction(session));

Начиная с Ninject 3.0.0, вы можете добавить привязку для HttpModules вместо регистрации их в web.config и использовать для них внедрение construcotr.Но поскольку HttpModule не знает, используется ли контекст, вам нужно открыть транзакцию для всех запросов.

...