在请求使用Ninject管理会话期间发生异常时,如何回滚nHibernate事务?

在请求使用Ninject管理会话期间发生异常时,如何回滚nHibernate事务?,nhibernate,transactions,ninject,rollback,Nhibernate,Transactions,Ninject,Rollback,我用nHibernate表示ORM,用Ninject表示IoC。 我根据一些自定义范围创建nHibernate会话(您可以假设它是根据请求创建的)。 我在激活时开始事务处理。 我在激活时提交事务 问题是,如果在请求期间发生异常,我希望回滚事务,而不是提交它。你知道如何检测(以一种干净的方式,最有可能使用Ninject上下文)发生了异常吗 注意:我不关心提交时可能发生的异常,我可以在下面的代码和角色中轻松捕获这些异常 protected void BindWithSessionWrapper<

我用nHibernate表示ORM,用Ninject表示IoC。 我根据一些自定义范围创建nHibernate会话(您可以假设它是根据请求创建的)。 我在激活时开始事务处理。 我在激活时提交事务

问题是,如果在请求期间发生异常,我希望回滚事务,而不是提交它。你知道如何检测(以一种干净的方式,最有可能使用Ninject上下文)发生了异常吗

注意:我不关心提交时可能发生的异常,我可以在下面的代码和角色中轻松捕获这些异常

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();
            });
}
我对激活进行了如下修改:

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

                        t.Session.Dispose();
                    });

它工作得很好,但是如果Ninject在发生异常时通过在上下文中设置一个标志来解决这个问题,那就更好了:)

如何实现
IHTTPModule
并订阅
错误
事件? 如所述

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

但是,请注意,如果请求没有使用会话,那么将创建一个会话,这是非常多余的

您可以执行一些操作,例如检查事务是否已启动,然后再回滚。但您仍然会创建一个不必要的会话

您可以通过使用
错误
事件处理程序在
HttpContext.Current.Items
上设置一个标志来进一步改进这一点,如

HttpContext.Current.Items["RollbackTransaction"] = true;
然后在会话的
激活中使用它,如:

    .OnDeactivation((c, t) => 
        { 
            if(HttpContext.Current.Items.Contains("RollbackTransaction"])
            {
                t.Session.Transaction.Rollback();
            }
            else
            {
                t.Session.Transaction.Commit();
            }
            t.Session.Dispose();
        });
请注意,
HttpContext
是线程本地的,这意味着当您切换线程时,它可能是
null
或者-最坏的情况-它甚至可能是另一个
HttpContext


还请注意,我无法尝试它,因此它可能无法工作。感谢反馈。

我不接受通过HttpContext传递状态,原因有二

  • HttpContext问题:)
  • 传递状态就像传递全局状态()
  • 经过多次尝试和错误,我认为这应该是一个解决方案: 假设我们正在处理WebApi项目,一旦遇到异常,所有操作都有回滚事务,使用Ninject:

  • 安装Ninject.Extension.Factory(),这对于将请求范围中的ISession注入过滤器来说是非常重要的一步
  • 使用以下配置绑定
    ISessionFactory
    ISession
    (我使用了这个示例:),再加上
    ISessionInRequestScopeFactory

  • 使用ninject filter injection将事务行为添加到每个操作():

  • 因此,我们现在将
    ApiTransactionFilter
    绑定到具有
    [ApiTransaction]
    属性的
    YourApiController

  • 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);
        }
    } 
    

  • 使用HttpContext是非常危险的:,HttpContext.Current在某些情况下可能会变为null。True,尤其是使用
    async/await
    时,这可能会变得非常危险。
    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.
    
    public interface ISessionInRequestScopeFactory
    {
        ISession CreateSessionInRequestScope(); // return ISession in the request scope
    }
    
    Kernel.BindHttpFilter<ApiTransactionFilter>(System.Web.Http.Filters.FilterScope.Action)
        .WhenControllerHas<ApiTransactionAttribute>();
    
     [ApiTransaction]
     public class YourApiController{ /* ... */}
    
    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);
        }
    }