factory.OpenSesion()上的NHibernate错误;该操作对于事务的状态无效";

factory.OpenSesion()上的NHibernate错误;该操作对于事务的状态无效";,nhibernate,sharepoint,transactions,distributed-transactions,Nhibernate,Sharepoint,Transactions,Distributed Transactions,我在使用NHibernate时遇到了一个非常奇怪的错误。当我调用opensession时,我得到了这个错误和堆栈跟踪 The operation is not valid for the state of the transaction.-at System.Transactions.TransactionState.EnlistVolatile(InternalTransaction tx, IEnlistmentNotification enlistmentNotification, Enl

我在使用NHibernate时遇到了一个非常奇怪的错误。当我调用opensession时,我得到了这个错误和堆栈跟踪

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源代码,查看抛出错误的方法。如堆栈跟踪中所述,它是“EnstractInDistributedTransactionIfRequired”方法。这是该方法的代码

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

其他一些可能相关的细节

  • 我的会话工厂由在Web应用程序/服务的生命周期内存在的静态对象管理
  • 调用我的方法的sharepoint进程是一个名为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;       
        }
    

    这可能是导致这种情况的代码中的一些奇怪的东西,或者是NHibernate中的一个bug(因此它无法处理Sharepoint中的一些奇怪情况),或者是Sharepoint中的一个bug

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


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

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

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

    在与我们的生产管理员交谈后,我发现大约在这个问题开始的时候,我们升级了一个第三方组件(Newsgator),该组件可以处理大量传入的电子邮件。因此,我认为他们的终端可能有一个bug,导致事务泄漏


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

    我已设法使用
    NHibernate.ISessionFactory.execute(System.Type persistentClass,object id)解决了完全相同的问题

    请注意,单独使用
    Session.Dispose()
    (没有上面的
    execute
    调用)没有帮助,因此使用了
    NHibernate.ISessionFactory.execute
    这一更为激进的方法,并愉快地帮助了我


    希望它能帮助其他人。

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