在具有Ninject管理会话的请求期间发生异常时如何回滚nHibernate事务?

时间:2014-10-23 01:32:25

标签: nhibernate transactions ninject rollback

我将nHibernate用于ORM,将Ninject用于IoC。 我为每个自定义范围创建了nHibernate会话(您可以根据请求假设)。 我开始onActivation交易。 我将事务提交到Deactivation。

问题是如果在请求期间发生异常,我想回滚事务而不是提交它。任何想法如何检测(以一种干净的方式,最可能使用Ninject Context)发生异常?

注意:我并不关心在提交时可能发生的异常,我可以轻松地在下面的代码和角色中找到它。

protected void BindWithSessionWrapper<T>(Func<IContext, T> creationFunc) where T : ISessionWrapper
{
    Bind<T>().ToMethod(creationFunc)
        .InScope(x => new NinjectCustomScope()) // work in progress !!!
        .OnActivation(t => t.Session.BeginTransaction(IsolationLevel.ReadCommitted))
        .OnDeactivation((c, t) => 
            { 
                t.Session.Transaction.Commit();
                t.Session.Dispose();
            });
}

更新

我遵循了@BatteryBackupUnit的建议。 所以我将以下内容添加到Error EventHandler:

    Error += (s, e) =>
        {
            HttpContext.Current.Items["ErrorRaised"] = true;
        };

我将OnDeactivation修改为如下所示:

OnDeactivation(t => 
                    { 
                        if ((bool?)HttpContext.Current.Items["ErrorRaised"] == true)
                            t.Session.Transaction.Rollback();
                        else
                            t.Session.Transaction.Commit();

                        t.Session.Dispose();
                    });

它工作正常,但如果Ninject通过在上下文中设置一个标志来解决这个问题会更好:如果发生异常:)

2 个答案:

答案 0 :(得分:1)

如何实施IHTTPModule并订阅Error活动? 与描述here

类似

Error事件处理程序中,使用System.Web.Mvc.DependencyResolver.Current.GetService(typeof (ISession))检索当前会话并回滚事务。

但请注意,如果请求未使用会话,则会创建一个会话,这是非常多余的。

您可能会执行某些操作,例如检查事务是否已启动,然后再将其回滚。但是你还是会不必要地创建一个会话。

您可以使用Error事件处理程序在HttpContext.Current.Items上设置标记来进一步改进,例如

HttpContext.Current.Items["RollbackTransaction"] = true;

然后在会话的OnDeactivation中使用它,如:

    .OnDeactivation((c, t) => 
        { 
            if(HttpContext.Current.Items.Contains("RollbackTransaction"])
            {
                t.Session.Transaction.Rollback();
            }
            else
            {
                t.Session.Transaction.Commit();
            }
            t.Session.Dispose();
        });

请注意,HttpContext是线程本地的,这意味着当您切换线程时,它可能是null或者 - 最坏情况 - 它甚至可能是另一个HttpContext

请注意,我无法尝试,所以它可能无法正常工作。反馈意见。

答案 1 :(得分:0)

由于两个原因,我无法接受通过HttpContext传递状态。

  1. HttpContext问题:https://stackoverflow.com/a/12219078/656430
  2. 传递状态似乎是通过全局状态(https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil
  3. 经过大量的反复试验,我认为这应该是一个解决方案: 假设我们正在开发WebApi项目,所有操作的回滚事务一旦命中异常,就使用Ninject:

    1. 安装 Ninject.Extension.Factory https://www.nuget.org/packages/Ninject.Extensions.Factory/),这是将请求范围内的ISession注入过滤器非常重要的一步。
    2. 使用以下配置绑定ISessionFactoryISession(我使用了此示例:Need a simple example of using nhibernate + unit of work + repository pattern + service layer + ninject),以及 ISessionInRequestScopeFactory

      Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
      Bind<ISession>()
              .ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession())
              .InRequestScope(); // notice that we don't need to call `BeginTransaction` at this moment 
      Bind<ISessionInRequestScopeFactory>().ToFactory(); // you don't need to make your implementation, the Ninject.Extension.Factory extension will help you so.
      
    3. 接口ISessionInRequestScopeFactory的代码:

      public interface ISessionInRequestScopeFactory
      {
          ISession CreateSessionInRequestScope(); // return ISession in the request scope
      }
      
    4. 对每个操作(https://github.com/ninject/Ninject.Web.WebApi/wiki/Dependency-injection-for-filters)使用 ninject过滤器注入 添加交易行为

      Kernel.BindHttpFilter<ApiTransactionFilter>(System.Web.Http.Filters.FilterScope.Action)
          .WhenControllerHas<ApiTransactionAttribute>();
      
    5. [ApiTransaction]属性添加到控制器:

       [ApiTransaction]
       public class YourApiController{ /* ... */}
      
    6. 所以我们现在将ApiTransactionFilter绑定到YourApiController [ApiTransaction]属性

    7. ApiTransactionFilter内,您应该扩展AbstractActionFilter注入工厂ISessionInRequestScopeFactory以获取正确的请求范围会话

      public class ApiTransactionFilter : AbstractActionFilter{
          private readonly ISessionInRequestScopeFactory factory;
      
          public ApiTransactionFilter(ISessionInRequestScopeFactory factory){
              this.factory = factory;
          }
      
          public override void OnActionExecuting(HttpActionContext actionContext)
          {
              ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory
              session.BeginTransaction(); // session can begin transaction here ... 
              base.OnActionExecuting(actionContext);
          }
      
          public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
          {
              ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory
              if (actionExecutedContext.Exception == null) // NO EXCEPTION!
              {
                  session.Transaction.Commit();// session commit here ... may be you like to have try catch here
              }
              else
              {
                 session.Transaction.Rollback(); // session rollback here ...
              }
      
              base.OnActionExecuted(actionExecutedContext);
          }
      }