使用 Nhibernate Repository 管理事务的最佳方法是什么
What is the best way to manage Transaction with Nhibernate Repository
实际上,我试图找到在 MVC 5 上下文中使用带有存储库模式的 Nhibernate 管理事务的最佳方法
您可以在这里找到我的示例项目:https://github.com/Nono31/Pixel.Sample
经理调用我的存储库
我的经理被控制器呼叫
实际上一切正常,但是当我启动 NHProfiler 时,我有一个警告 "Use of implicit transactions is discouraged"
(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();
}
}
}
}
}
您应该处理整个工作单元。工作单元应涵盖您为获取视图模型所做的工作。
这样做可以避免在事务外发生延迟加载。
它还允许您在出错时回滚整个工作单元。
并且它具有性能优势:NHProfiler 警告的原因之一是为每次数据访问打开事务的成本。 (还有其他的,比如需要显式事务的二级缓存,否则它会在更新时被禁用。)
您可以使用您找到的UnitOfWorkAction
。
我个人也觉得'broad'。它包括交易甚至结果执行。这允许在视图中使用延迟加载。我认为我们不应该使用实体作为视图模型,在我看来从视图触发数据库访问更糟糕。我用的那个在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 的精彩博客系列:
- Refactoring, baseline
- Refactoring, global state
- Refactoring, session scope
- Refactoring, broken
- Refactoring, view model
- Refactoring, globals
- 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;
}
}
我将当前会话保留为 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();
}
然后我有一个事务属性,您可以用它来标记每个控制器操作。
[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;
}
实际上,我试图找到在 MVC 5 上下文中使用带有存储库模式的 Nhibernate 管理事务的最佳方法
您可以在这里找到我的示例项目:https://github.com/Nono31/Pixel.Sample 经理调用我的存储库 我的经理被控制器呼叫
实际上一切正常,但是当我启动 NHProfiler 时,我有一个警告 "Use of implicit transactions is discouraged" (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();
}
}
}
}
}
您应该处理整个工作单元。工作单元应涵盖您为获取视图模型所做的工作。
这样做可以避免在事务外发生延迟加载。
它还允许您在出错时回滚整个工作单元。
并且它具有性能优势:NHProfiler 警告的原因之一是为每次数据访问打开事务的成本。 (还有其他的,比如需要显式事务的二级缓存,否则它会在更新时被禁用。)
您可以使用您找到的UnitOfWorkAction
。
我个人也觉得'broad'。它包括交易甚至结果执行。这允许在视图中使用延迟加载。我认为我们不应该使用实体作为视图模型,在我看来从视图触发数据库访问更糟糕。我用的那个在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 的精彩博客系列:
- Refactoring, baseline
- Refactoring, global state
- Refactoring, session scope
- Refactoring, broken
- Refactoring, view model
- Refactoring, globals
- 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;
}
}
我将当前会话保留为 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();
}
然后我有一个事务属性,您可以用它来标记每个控制器操作。
[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;
}