1

我在使用 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 不为空时才真正执行任何操作。就我而言,我无法理解为什么它不会为空,因为尝试打开会话的方法不会打开任何其他会话或事务,但我不是分布式事务方面的专家。

其他一些可能相关的细节

  1. 我的会话工厂由在 web 应用程序/服务的生命周期中存在的静态对象管理。
  2. 调用我的方法的共享点进程是一个名为 OWSTimer 的 Windows 服务。据我了解(但尚未确认),它为每封传入的电子邮件生成一个单独的线程,然后在该线程上调用我的代码。
  3. 我不需要分布式事务,所以如果我可以强制 NHibernate 永远不要在分布式事务中登记我的会话,那也很好。
  4. 我有许多与会话交互的事件侦听器。在所有情况下,我都会通过调用 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;       
    }
4

3 回答 3

2

我想我已经确定了一种解决方法并有一个关于原因的理论。据我所知,仅当属性 System.Transactions.Transaction.Current 不为 null 并且当前事务被中止时才会发生此错误。查看 Nhibernate 的代码,没有任何与 System.Transactions.Transaction.Current 或 TransactionScope 类交互的方式,以至于我的任何代码都可能导致 Nhibernate 创建这种场景。无论如何,我自己的代码也不直接使用 System.Transactions,因此我所做的任何事情都不太可能导致泄漏的中止事务。

但是,经过测试,我发现大多数与电子邮件处理相关的 OWSTimer 代码似乎都在单个线程上运行。因此,我怀疑在我们的环境中部署的与处理传入电子邮件相关的任何其他自定义代码都在与我的代码相同的线程上运行。其他一些组件中的错误可能会泄漏此事务并破坏对 NHibernate 的后续调用。

在与我们的生产管理员交谈后,我发现大约在这个问题开始的时候,我们升级了一个第三方组件(Newsgator),它对传入的电子邮件有很多作用。结果,我认为他们的一端可能存在错误,导致交易泄漏。

为了防御它,我正在修改我的会话管理代码,以在打开新会话之前检查 System.Transactions.Transaction.Current 是否包含并中止事务。如果是这样,那么我将自己处理并取消该交易。

于 2013-01-24T15:35:43.757 回答
0

这要么是你的代码中的一些奇怪的东西导致了这种情况,要么是 NHibernate 中的一个错误(因此它无法处理一些 Sharepoint 的怪异),或者是 Sharepoint 中的一个错误。

在任何一种情况下,禁用此代码都感觉像是一种解决方法,而不是解决真正的问题。然而,这样做是可能的。查看 NHibernate 源代码,您可以使用另一个事务工厂。查看NHibernate.Cfg.Environment,你会找到配置参数来设置它。

(即将用完,所以现在无法查看详细信息。)

于 2013-01-23T19:49:42.590 回答
0

我已经设法解决了这个完全相同的场景NHibernate.ISessionFactory.Evict(System.Type persistentClass, object id);

请注意,Session.Dispose()单独(没有上述Evict调用)没有帮助,因此使用了更激烈的方法NHibernate.ISessionFactory.Evict并愉快地帮助了我。

希望它可以帮助别人。

于 2016-10-26T07:15:57.860 回答