何时在ASP.NET MVC 2应用程序中提交NHibernate事务?

何时在ASP.NET MVC 2应用程序中提交NHibernate事务?,nhibernate,asp.net-mvc-2,transactions,commit,Nhibernate,Asp.net Mvc 2,Transactions,Commit,首先是一些背景知识:我对ASP.NETMVC2和NHibernate不熟悉。我正在启动我的第一个应用程序,我想使用NHibernate,因为我来自JSP+Struts 1+Hibernate web应用程序 似乎没有人在谈论这件事,所以我想这件事一定很明显。我还是挠头,因为我找不到一个解决方案来完成以下事情: 1) 我想使用“每请求会话”策略。因此,每次用户发出请求时,他都会获得一个Nhibernate会话,启动一个事务,当请求结束时,事务提交,Nhibernate会话关闭(如果有,则返回池)。

首先是一些背景知识:我对ASP.NETMVC2和NHibernate不熟悉。我正在启动我的第一个应用程序,我想使用NHibernate,因为我来自JSP+Struts 1+Hibernate web应用程序

似乎没有人在谈论这件事,所以我想这件事一定很明显。我还是挠头,因为我找不到一个解决方案来完成以下事情:

1) 我想使用“每请求会话”策略。因此,每次用户发出请求时,他都会获得一个Nhibernate会话,启动一个事务,当请求结束时,事务提交,Nhibernate会话关闭(如果有,则返回池)。这保证了我的事务是原子的

2) 当发生数据库异常(PK违规、唯一违规等)时,我希望捕获该异常,回滚我的事务并给用户一条明确的消息:如果是PK违规,则是该消息,所有完整性错误也是如此

那么,我的问题是什么?我来自Java世界,在那里我使用过滤器打开会话,启动事务,处理请求,然后提交事务并关闭会话。这是可行的,除非发生DB异常,并且当您在筛选器中时,由于响应已经提交,因此无法更改目标页面

因此,当事务实际回滚时,用户会看到成功页面。为了避免这种情况,我必须用Java编写大量数据完整性检查,以防止所有完整性异常,因为我无法正确处理它们。这是不好的,因为我正在做的工作,而不是把它留给数据库(或者可能我错了,我总是要在我的应用程序中编写所有这些数据完整性代码?)

所以我发现IHttpModule接口,我猜它与javax.servlet.Filter的概念几乎相同(如果我错了,请纠正我),所以我猜我可能会再次遇到同样的问题

我应该把我的提交放在哪里,以确保我的事务是原子的,当它们抛出异常时,我可以捕获它们并更改我的目标页面,并向用户提供全面的消息

到目前为止,我提出的唯一可能的解决方案是保持IHttpModule启动和关闭事务,并将提交调用放在controllers方法的最后一行,从而能够在那里捕获异常,然后返回带有消息的适当视图。现在,我必须将这些提交和异常处理行复制到所有需要提交的控制器方法中。还有一个关注点分离的问题,我的控制器必须知道DB,我一点都不喜欢


有更好的方法吗?

如果您使用的是ASP.NET MVC,则可以使用来实现相同的效果

类似于(这是从我的体系结构的不同部分拼凑而成的):

公共类TransactionalAttribute:ActionFilterAttribute、IAuthorizationFilter、IEExceptionFilter { ITransaction transaction=NullTransaction.Instance; 公共隔离级别隔离级别{get;set;} 公共事务属性() { IsolationLevel=IsolationLevel.ReadCommitted; } 公共覆盖无效OnResultExecuted(ResultExecutedContext筛选器上下文) { 尝试 { Commit(); transaction=NullTransaction.Instance; } 捕获(异常) { Log.For(this.FatalFormat)(“尝试提交事务{0}时出现问题”,异常); } } 公共覆盖无效OnActionExecuting(ActionExecutingContext filterContext) { 如果(transaction==NullTransaction.Instance)transaction=UnitOfWork.Current.BeginTransaction(IsolationLevel); } 公共覆盖无效OnActionExecuted(ActionExecutedContext筛选器上下文) { if(filterContext.Result!=null)返回; Commit(); transaction=NullTransaction.Instance; } 授权时的公共无效(AuthorizationContext filterContext) { 事务=UnitOfWork.Current.BeginTransaction(IsolationLevel); } 公共无效OneException(例外上下文筛选器上下文) { 尝试 { transaction.Rollback(); transaction=NullTransaction.Instance; } 捕获(异常) { Log.For(this.FatalFormat)(“尝试回滚事务{0}的问题”,异常); } } 私有类NullTransaction:ITransaction { 公共静态ITransaction实例{get{return Singleton.Instance;}} 公共空间处置() { } 公共无效提交() { } 公共无效回滚() { } } }
经过思考并与同事讨论,我想出了一个几乎满足我所有需求的解决方案

我用Java项目实现了这个解决方案,效果非常好。我会放弃这个想法,这样每个人都可以在任何框架内使用它

解决方案在于将提交调用放在控制器方法的最后一行,即try-catch块中。如果发生约束异常,则可以获取违反约束的名称。使用该名称,您可以准确地告诉用户出了什么问题。我使用一个属性文件来存储要向用户显示的消息,该消息违反了约束。属性文件的键是约束名称,值是约束冲突消息

你可以将commit-handle\u exception-find\u constraint\u消息重构为一个方法,这就是我所做的

现在,它解决了我编写代码检查数据库完整性的问题,我相信在属性文件中包含约束冲突消息是非常优雅的。现在,我仍然不喜欢我的控制器需要调用t的想法
public class TransactionalAttribute : ActionFilterAttribute, IAuthorizationFilter, IExceptionFilter
{

    ITransaction transaction = NullTransaction.Instance;
    public IsolationLevel IsolationLevel { get; set; } 

    public TransactionalAttribute() 
    {
        IsolationLevel = IsolationLevel.ReadCommitted;
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        try
        {
            transaction.Commit();
            transaction = NullTransaction.Instance;
        }
        catch (Exception exception)
        {
            Log.For(this).FatalFormat("Problem trying to commit transaction {0}", exception);
        }

    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (transaction == NullTransaction.Instance) transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result != null) return;

        transaction.Commit();
        transaction = NullTransaction.Instance;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
    }

    public void OnException(ExceptionContext filterContext)
    {
        try
        {
            transaction.Rollback();
            transaction = NullTransaction.Instance;
        }
        catch (Exception exception)
        {
            Log.For(this).FatalFormat("Problem trying to rollback transaction {0}", exception);
        }
    }

    private class NullTransaction : ITransaction
    {
        public static ITransaction Instance { get { return Singleton<NullTransaction>.Instance; } }

        public void Dispose()
        {

        }

        public void Commit()
        {
        }

        public void Rollback()
        {
        }
    }
}