【问题标题】:What is the best way to manage Transaction with Nhibernate Repository使用 Nhibernate 存储库管理事务的最佳方法是什么
【发布时间】:2016-07-28 19:35:01
【问题描述】:

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

你可以在这里找到我的示例项目: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();
                    }
                }
        }
    }
}

【问题讨论】:

  • 哪种方法能准确解决这个 NProf 问题?您的存储库看起来不错,并且使用委托包装器解决方案似乎完全有效。
  • 对不起,我所有的 select 方法都在 FooController 示例中的事务中调用,但例如,如果您可以 Foo.Bar,其中 Bar 引用了我的控制器中的 foo 对象,则 Bar 选择将超出事务

标签: .net tsql nhibernate transactions autofac


【解决方案1】:

我将当前会话作为一个属性保存在 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;
    }

【讨论】:

  • 您好,感谢您的回答,但 Application_BeginRequest 是否不会在资源(css...)请求时触发?
  • 它不应该在 iis 中。
  • 您好,经过一番检查,您是对的,但我更喜欢使用 autofac 注册新会话,因为它仅在需要时才解析新会话 InstancePerRequest()(如果数据访问)
  • 如果启用了rammfarBeginRequest 可能会在 IIS 中的静态资源上被调用。但是这个东西不应该启用。当一个旧的 IIS 错误导致无扩展 URI 出现问题时,它的使用很流行。
【解决方案2】:

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

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

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

您可以使用您找到的UnitOfWorkAction
就个人而言,我觉得它太“宽泛”了。它甚至包括在事务中的结果执行。这允许在视图中使用延迟加载。我认为我们不应该使用实体作为视图模型,并且在我看来从视图触发数据库访问更糟糕。我使用的那个在OnActionExecuted结束交易。
此外,它的错误处理在我看来有点具体。对无效模型状态进行回滚可能没有意义:不应该尝试将无效数据保存在数据库中。不回滚处理的异常对于 less 来说很奇怪:如果 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

因为我的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;
    }
}

【讨论】:

  • 感谢您对 ActionAttribute 的改进,我不想单独处理每个数据访问,这就是为什么我的存储库库包含 if (Session.Transaction != null && Session.Transaction.IsActive) actionToExecute() ;但我想知道每种方式都可以管理我的交易并探索诸如 Castle 的自动交易管理或其他解决方案
  • 请注意,使用事务操作过滤器,如果需要,您可能仍然有许多事务用于一个操作。对于这种情况,我的模式是在我需要在自己的事务中进行一些处理时提交当前正在运行的事务。然后我重新打开一个新的,进行处理,提交它(或在错误时回滚),然后为其余的操作重新打开第三个事务(并且让我的默认事务过滤器保持“快乐”)。我的IUnitOfWork 确实更像是一个“工作单元”提供者,而不是一个“工作单元”。我已将其添加到我的答案中。
  • 我喜欢这个想法,也许在某些方法调用的业务层中使用 DI 拦截器实现您的 UoW 是一个可能的解决方案
猜你喜欢
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 2015-06-20
  • 1970-01-01
  • 2019-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多