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)

これは私のアプリケーションの1つの場所でのみ発生しており、それでも一貫して発生しているわけではありません。具体的には、これは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でない場合にのみ実際に何も実行しません。私の場合、セッションを開こうとするメソッドは他のセッションやトランザクションを開かないため、nullにならない理由は理解できませんが、分散トランザクションの専門家ではありません。

関連する可能性のある他のいくつかの詳細

  1. 私のセッションファクトリは、Webアプリケーション/サービスの存続期間中存在する静的オブジェクトによって管理されます。
  2. 私のメソッドを呼び出すSharePointプロセスは、OWSTimerと呼ばれるWindowsサービスです。私が理解していることから(しかし確認していませんが)、受信メールごとに個別のスレッドを生成し、そのスレッドでコードを呼び出します。
  3. 分散トランザクションは必要ないので、NHibernateにセッションを分散トランザクションに参加させないように強制できれば、それでも問題ありません。
  4. セッションと対話するイベントリスナーがいくつかあります。すべての場合において、var session = @ event.Session.GetSession(EntityMode.Poco);を呼び出して、イベントリスナーでセッションインスタンスを取得しています。ドキュメントによると、セッションのこれらのインスタンスを閉じません。ただし、ドキュメントのその部分を見逃したという理由だけで、フラッシュと呼ぶ場合もあります。

更新:これは、セッションの作成を処理し、他のコードによって呼び出される静的メソッドです。

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 に答える