我在使用 NHibernate 时遇到了一个非常奇怪的错误。当我调用打开会话时,我收到此错误和堆栈跟踪。
The operation is not valid for the state of the transaction.-at System.Transactions.TransactionState.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions, Transaction atomicTransaction)
at System.Transactions.Transaction.EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
at NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.EnlistInDistributedTransactionIfNeeded(ISessionImplementor session)
at NHibernate.Impl.AbstractSessionImpl.CheckAndUpdateSessionStatus()
at NHibernate.Impl.SessionImpl..ctor(IDbConnection connection, SessionFactoryImpl factory, Boolean autoclose, Int64 timestamp, IInterceptor interceptor, EntityMode entityMode, Boolean flushBeforeCompletionEnabled, Boolean autoCloseSessionEnabled, ConnectionReleaseMode connectionReleaseMode)
at NHibernate.Impl.SessionFactoryImpl.OpenSession(IDbConnection connection, Boolean autoClose, Int64 timestamp, IInterceptor sessionLocalInterceptor)
at NHibernate.Impl.SessionFactoryImpl.OpenSession(IInterceptor sessionLocalInterceptor)
这只发生在我的应用程序的一个地方,即使那样也不是一致的。具体来说,这是在 SharePoint 应用程序中运行的代码。每当 SharePoint 在特定地址收到电子邮件时,它就会触发我的代码。我之所以提出这一点,是因为要注意,每次调用代码时,它都在单独的线程上运行,并且该线程上没有现有的 NHibernate 事务或会话。
我破解了 NHibernate 源代码,查看了抛出错误的方法。如堆栈跟踪中所述,它是“EnlistInDistributedTransactionIfNeeded”方法。这是该方法的代码
if (session.TransactionContext != null)
return;
if (System.Transactions.Transaction.Current == null)
return;
var transactionContext = new DistributedTransactionContext(session,
System.Transactions.Transaction.Current);
session.TransactionContext = transactionContext;
logger.DebugFormat("enlisted into DTC transaction: {0}",
transactionContext.AmbientTransation.IsolationLevel);
session.AfterTransactionBegin(null);
transactionContext.AmbientTransation.TransactionCompleted +=
delegate(object sender, TransactionEventArgs e)
{
using (new SessionIdLoggingContext(session.SessionId))
{
((DistributedTransactionContext)session.TransactionContext).IsInActiveTransaction = false;
bool wasSuccessful = false;
try
{
wasSuccessful = e.Transaction.TransactionInformation.Status
== TransactionStatus.Committed;
}
catch (ObjectDisposedException ode)
{
logger.Warn("Completed transaction was disposed, assuming transaction rollback", ode);
}
session.AfterTransactionCompletion(wasSuccessful, null);
if (transactionContext.ShouldCloseSessionOnDistributedTransactionCompleted)
{
session.CloseSessionFromDistributedTransaction();
}
session.TransactionContext = null;
}
};
transactionContext.AmbientTransation.EnlistVolatile(transactionContext,
EnlistmentOptions.EnlistDuringPrepareRequired);
如您所见,此方法仅在 System.Transactions.Transaction.Current 不为空时才真正执行任何操作。就我而言,我无法理解为什么它不会为空,因为尝试打开会话的方法不会打开任何其他会话或事务,但我不是分布式事务方面的专家。
其他一些可能相关的细节
- 我的会话工厂由在 web 应用程序/服务的生命周期中存在的静态对象管理。
- 调用我的方法的共享点进程是一个名为 OWSTimer 的 Windows 服务。据我了解(但尚未确认),它为每封传入的电子邮件生成一个单独的线程,然后在该线程上调用我的代码。
- 我不需要分布式事务,所以如果我可以强制 NHibernate 永远不要在分布式事务中登记我的会话,那也很好。
- 我有许多与会话交互的事件侦听器。在所有情况下,我都会通过调用 var session = @event.Session.GetSession(EntityMode.Poco); 在事件侦听器中获取会话实例;根据文档,我不会关闭这些会话实例。但是,在某些情况下,我确实调用了flush,只是因为我错过了文档的那一部分。
更新:这是处理创建会话并由我的其他代码调用的静态方法。
public static ISession CreateAuditableSession(string siteUrl, ISharePointDataContext context)
{
var factory = Instance(siteUrl);
var session = factory.OpenSession();
var imp = session.GetSessionImplementation();
imp.Listeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { new AuditUpdateListener(context) };
imp.Listeners.PostInsertEventListeners = new IPostInsertEventListener[] { new AuditUpdateListener(context) };
imp.Listeners.FlushEventListeners = new IFlushEventListener[] { new FixedDefaultFlushEventListener() };
imp.Listeners.PreInsertEventListeners = new IPreInsertEventListener[] { new PGUserDisplayNameRetrieverListener(context) };
imp.Listeners.PreDeleteEventListeners = new IPreDeleteEventListener[] { new AuditUpdateListener(context) };
imp.Listeners.PostCollectionUpdateEventListeners = new IPostCollectionUpdateEventListener[] { new AuditUpdateListener(context), new SupplierChangeEventListener() };
imp.Listeners.PostCollectionRecreateEventListeners = new IPostCollectionRecreateEventListener[] { new AuditUpdateListener(context), new SupplierChangeEventListener() };
imp.Listeners.PostLoadEventListeners = new IPostLoadEventListener[] { new PostLoadSubscriptionAndInjectionEventListener(context) };
return session;
}