【问题标题】:NHibernate Error on factory.OpenSesion() "The operation is not valid for the state of the transaction"factory.OpenSesion() 上的 NHibernate 错误“操作对于事务的状态无效”
【发布时间】: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 时才真正执行任何操作。就我而言,我无法理解为什么它不会为空,因为尝试打开会话的方法不会打开任何其他会话或事务,但我不是分布式事务方面的专家。

其他一些可能相关的细节

  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;       
    }

【问题讨论】:

  • 你能展示你使用会话和事务的代码吗?
  • 添加了代码并调用了您在其中提到的事件侦听器的一些详细信息
  • 您应该只需要在 SessionFactory 创建时设置一次侦听器......这可能是问题所在。另外,您是否正在处理会话并使用显式事务?
  • 我必须在每个会话的上下文中设置侦听器,因为它们依赖于某些仅在请求时可用的数据。特别是对于审核,我可以获得当前用户的唯一方法是传入一个 SharePointDataContext 对象(一个用于处理 SharePoint 通信并允许测试的自定义类)。该对象只能为每个请求重新创建,而不是作为应用程序生命周期中存在的静态对象。至于第二条评论,是的。会话和显式事务都包装在实际调用代码中的 using 块中。

标签: nhibernate sharepoint transactions distributed-transactions


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    (即将用完,暂时无法查看详情。)

    【讨论】:

    • 这让我解决了这个问题。这是在 FluentNHibernate 中设置它的代码: configuration.ExposeConfiguration(c => { c.SetProperty( "transaction.factory_class", typeof (global::NHibernate.Transaction.AdoNetTransactionFactory).AssemblyQualifiedName ); });
    【解决方案3】:

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

    请注意,单独使用Session.Dispose()(没有上面的Evict 调用)没有帮助,因此使用了NHibernate.ISessionFactory.Evict 更激烈的方法,并且很高兴帮助了我。

    希望它可以帮助其他人。

    【讨论】:

      猜你喜欢
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多