使用Nhibernate存储库管理事务的最佳方法是什么

时间:2016-04-07 18:25:45

标签: .net tsql nhibernate transactions autofac

实际上我试图在MVC 5上下文中找到使用Nhibernate和Repository模式管理事务的最佳方法

您可以在此处找到我的示例项目:https://github.com/Nono31/Pixel.Sample 我的存储库由经理调用 我的经理被控制器召唤

实际上一切正常,但是当我启动NHProfiler时,我发出警告“不鼓励使用隐式交易” (http://www.hibernatingrhinos.com/products/nhprof/learn/alert/donotuseimplicittransactions

我的问题是如何在我的上下文中避免隐式事务? 在哪一层管理交易? 如果我在Repository层管理我的事务,则在事务之外调用延迟加载实体。 我见过一个使用ActionFilterAttribute的解决方案,但还有其他解决方案吗?

public class UnitOfWorkAction : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        base.OnActionExecuting(context);

        if (!context.IsChildAction)
        {
            var session = DependencyResolver.Current.GetService<ISession>();
            session.BeginTransaction();
        }
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        base.OnResultExecuted(context);

        if (context.IsChildAction) return;

        using (var session = DependencyResolver.Current.GetService<ISession>())
        {
            if (session.Transaction != null && session.Transaction.IsActive)
                using (var transaction = session.Transaction)
                {
                    try
                    {
                        var thereWereNoExceptions = context.Exception == null || context.ExceptionHandled;
                        if (context.Controller.ViewData.ModelState.IsValid && thereWereNoExceptions)
                            transaction.Commit();
                        else
                            transaction.Rollback();
                    }
                    catch
                    {
                        transaction.Rollback();
                        throw;
                    }
                    finally
                    {
                        session.Close();
                    }
                }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

我将当前会话保留为global.asax中的属性,并在BeginRequest上打开它。

    public static ISession CurrentSession
    {
        get { return (ISession)HttpContext.Current.Items[sessionkey]; }
        set { HttpContext.Current.Items[sessionkey] = value; }
    }

    protected void Application_BeginRequest()
    {
        CurrentSession = SessionFactory.OpenSession();
    }

    protected void Application_EndRequest()
    {
        if (CurrentSession != null)
            CurrentSession.Dispose();
    }

然后我有一个Transaction属性,你可以用。标记每个控制器动作。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : ActionFilterAttribute
{
    private ITransaction Transaction { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Transaction = MvcApplication.CurrentSession.BeginTransaction(IsolationLevel.ReadCommitted);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (Transaction.IsActive)
        {
            if (filterContext.Exception == null)
            {
                Transaction.Commit();
            }
            else
            {
                Transaction.Rollback();
            }
        }
    }
}

在我的存储库中,我有一个事务方法,如果当前没有一个事务,它将启动一个事务。

    protected virtual TResult Transact<TResult>(Func<TResult> func)
    {
        if (_session.Transaction.IsActive)
            return func.Invoke();

        TResult result;
        using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
        {
            result = func.Invoke();
            tx.Commit();
        }

        return result;
    }

答案 1 :(得分:1)

您应该处理整个工作单元。工作单元应涵盖您为获取视图模型所做的工作。

这样做可以避免在事务中发生延迟加载。

如果出现错误,它还允许您回滚整个工作单元 它具有性能优势:NHProfiler警告的原因之一是为每个数据访问打开事务的成本。 (还有其他一些,例如需要显式事务的二级缓存,否则在更新时会被禁用。)

您可以使用找到的UnitOfWorkAction 就个人而言,我发现它太'宽泛'。它包括在事务中甚至结果执行。这允许在视图中使用延迟加载。我认为我们不应该使用实体作为视图模型,并且在我看来,从视图触发数据库访问更加糟糕。我使用的那个以OnActionExecuted结束交易 此外,它的错误处理在我看来有点具体。无效模型状态的回滚可能不会产生敏感:不应该尝试在DB中保存无效数据。对于已处理的异常,不支持回滚是很奇怪的:如果MVC管道遇到异常,这意味着操作或执行结果时出现问题,但是其他一些过滤器已经处理了异常。通常,只是显示错误页面的错误过滤器,不是吗?然后这样的逻辑导致提交失败的动作......

这是我使用的模式:

public class DefaultTransactionAttribute : ActionFilterAttribute
{
    private static readonly ILog Logger =
        LogManager.GetLogger(typeof(DefaultTransactionAttribute));

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // IUnitOfWork is some kind of custom ISession encapsulation.
        // I am working in a context in which we may change the ORM, so
        // I am hiding it.
        var uow = DependencyResolver.Current.GetService<IUnitOfWork>();
        uow.BeginTransaction();
        base.OnActionExecuting(filterContext);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        var uow = DependencyResolver.Current.GetService<IUnitOfWork>();
        if (!uow.HasActiveTransaction())
        {
            // Log rather than raise an exception, for avoiding hiding
            // another failure.
            Logger.Warn("End of action without a running transaction. " +
                "Check how this can occur and try avoid this.");
            return;
        }
        if (filterContext.Exception == null)
        {
            uow.Commit();
        }
        else
        {
            try
            {
                uow.Rollback();
            }
            catch(Exception ex)
            {
                // Do not let this new exception hide the original one.
                Logger.Warn("Rollback failure on action failure. (If the" +
                    "transaction has been roll-backed on db side, this is" +
                    "expected.)", ex);
            }
        }
    }
}

有关最小MVC模式的逐步说明,请参阅Ayende的这篇精彩博客系列:

  1. Refactoring, baseline
  2. Refactoring, global state
  3. Refactoring, session scope
  4. Refactoring, broken
  5. Refactoring, view model
  6. Refactoring, globals
  7. Refactoring, transactions
  8. 由于我的IUnitOfWork有一些特殊的语义可以帮助我使用NHibernate的MVC模式,所以它是:

    // This contract is not thread safe and must not be shared between threads.
    public interface IUnitOfWork
    {
        /// <summary>
        /// Save changes. Generally unneeded: if a transaction is ongoing,
        /// its commit does it too.
        /// </summary>
        void SaveChanges();
        void CancelChanges();
        bool HasActiveTransaction();
        void BeginTransaction();
        /// <summary>
        /// Saves changes and commit current transaction.
        /// </summary>
        void Commit();
        void Rollback();
        /// <summary>
        /// Encapsulate some processing in a transaction, committing it if
        /// no exception was sent back, roll-backing it otherwise.
        /// The <paramref name="action"/> is allowed to rollback the transaction
        /// itself for cancelation purposes. (Commit supported too.)
        /// Nested calls not supported (InvalidOperationException). If the
        /// session was having an ongoing transaction launched through direct 
        /// call to <c>>BeginTransaction</c>, it is committed, and a new
        /// transaction will be opened at the end of the processing.
        /// </summary>
        /// <param name="action">The action to process.</param>
        void ProcessInTransaction(Action action);
        /// <summary>
        /// Encapsulate some processing in a transaction, committing it if
        /// no exception was sent back, roll-backing it otherwise.
        /// The <paramref name="function"/> is allowed to rollback the transaction
        /// itself for cancellation purposes. (Commit supported too.)
        /// Nested calls not supported (InvalidOperationException). If the
        /// session was having an ongoing transaction launched through direct
        /// call to <c>>BeginTransaction</c>, it is committed, and a new
        /// transaction will be opened at the end of the processing.
        /// </summary>
        /// <param name="function">The function to process.</param>
        /// <typeparam name="T">Return type of
        /// <paramref name="function" />.</typeparam>
        /// <returns>The return value of the function.</returns>
        T ProcessInTransaction<T>(Func<T> function);
    }
    
    public class UnitOfWork : IUnitOfWork
    {
        private static readonly ILog Logger =
            LogManager.GetLogger(typeof(UnitOfWork));
    
        private ISession Session;
    
        public UnitOfWork(ISession session)
        {
            Session = session;
        }
    
        public void SaveChanges()
        {
            Session.Flush();
        }
    
        public void CancelChanges()
        {
            Session.Clear();
        }
    
        public bool HasActiveTransaction()
        {
            return Session.Transaction.IsActive;
        }
    
        public void BeginTransaction()
        {
            Session.BeginTransaction();
        }
    
        public void Commit()
        {
            Session.Transaction.Commit();
        }
    
        public void Rollback()
        {
            Session.Transaction.Rollback();
        }
    
        public void ProcessInTransaction(Action action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            ProcessInTransaction<object>(() =>
            {
                action();
                return null;
            });
        }
    
        private bool _processing = false;
    
        public T ProcessInTransaction<T>(Func<T> function)
        {
            if (function == null)
                throw new ArgumentNullException("function");
            if (_processing)
                throw new InvalidOperationException(
                    "A transactional process is already ongoing");
    
            // Handling default transaction.
            var wasHavingActiveTransaction = Session.Transaction.IsActive;
            if (wasHavingActiveTransaction)
                Commit();
    
            BeginTransaction();
            T result;
            _processing = true;
            try
            {
                result = function();
            }
            catch
            {
                try
                {
                    if(Session.Transaction.IsActive)
                        Rollback();
                }
                catch (Exception ex)
                {
                    // Do not let this new exception hide the original one.
                    Logger.Error("An additional error occurred while " +
                        "attempting to rollback a transaction after a failed " +
                        "processing.", ex);
                }
                // Let original exception flow untouched.
                throw;
            }
            finally
            {
                _processing = false;
            }
            if (Session.Transaction.IsActive)
                Commit();
    
            if (wasHavingActiveTransaction)
                BeginTransaction();
    
            return result;
        }
    }