【发布时间】:2013-01-23 18:11:21
【问题描述】:
我在使用 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 不为 null 时才真正执行任何操作。就我而言,我无法理解为什么它不会为空,因为尝试打开会话的方法不会打开任何其他会话或事务,但我不是分布式事务方面的专家。
其他一些可能相关的细节
- 我的会话工厂由一个静态对象管理,该对象在 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;
}
【问题讨论】:
-
你能展示你使用会话和事务的代码吗?
-
添加了代码并调用了您在其中提到的事件侦听器的一些详细信息
-
您应该只需要在 SessionFactory 创建时设置一次侦听器......这可能是问题所在。另外,您是否正在处理会话并使用显式事务?
-
我必须在每个会话的上下文中设置侦听器,因为它们依赖于某些仅在请求时可用的数据。特别是对于审核,我可以获得当前用户的唯一方法是传入一个 SharePointDataContext 对象(一个用于处理 SharePoint 通信并允许测试的自定义类)。该对象只能为每个请求重新创建,而不是作为应用程序生命周期中存在的静态对象。至于第二条评论,是的。会话和显式事务都包装在实际调用代码中的 using 块中。
标签: nhibernate sharepoint transactions distributed-transactions