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にならない理由は理解できませんが、分散トランザクションの専門家ではありません。
関連する可能性のある他のいくつかの詳細
- 私のセッションファクトリは、Webアプリケーション/サービスの存続期間中存在する静的オブジェクトによって管理されます。
- 私のメソッドを呼び出すSharePointプロセスは、OWSTimerと呼ばれるWindowsサービスです。私が理解していることから(しかし確認していませんが)、受信メールごとに個別のスレッドを生成し、そのスレッドでコードを呼び出します。
- 分散トランザクションは必要ないので、NHibernateにセッションを分散トランザクションに参加させないように強制できれば、それでも問題ありません。
- セッションと対話するイベントリスナーがいくつかあります。すべての場合において、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;
}