ISession はスレッドセーフではなく、SessionFactory はスレッドセーフであることを理解しています。そのため、スレッドごとに 1 つのセッションがあることをラップして確認しました。
次の状況でエラーが発生しました。これがサポートされていないものなのか、それとも ISession スレッドの分離でまだ何かが欠けているのか疑問に思っていました。
NUnit テストを実行しています。エンティティがフィールド変数としてスタブ化されるシナリオがあります。2 つの並列タスクを実行するテストがあります。
• 各並列タスクは、同じ SessionFactory から独自のセッションを作成し、NHibernate トランザクションを開始します。
• それぞれエンティティを更新し、それに対して SaveOrUpdate を実行します。
• 次に、トランザクションをコミットして閉じます。
各タスクはこれを約 10,000 回行います。
このテスト中に、次のメッセージが表示されます。
System.AggregateException : One or more errors occurred.
----> NHibernate.HibernateException : identifier of an instance of Domain.Entity.MyEntity was altered from 2 to 1
MyEntity はフィールド オブジェクトであり、両方のスレッドによって消費されるため、これは理にかなっています。したがって、NUnit クラスで作成された単一のオブジェクトは、両方のスレッドによって参照および更新されます。
私の質問は、悲観的ロックまたは他の NHibernate 機能によって、このようなシナリオを回避できるかどうかです。それとも、これは実行できないので、この状況 (つまり、Entity オブジェクトが一度に複数のスレッドによって参照および更新されない) がコードで発生しないようにする必要がありますか?
エンティティのバージョン管理を確実にし、いくつかのロック呼び出しを試みたなど、NHibernate のいくつかのオプションにうんざりしましたが、このシナリオを処理するための正しい方法であるドキュメントを暗闇の中で推測しています。
追記:コメントありがとうございます!単体テストのコードは次のとおりです。
private PluginConfiguration _configStub1;
[SetUp]
public void Setup()
{
new FluentMapper().Configuration().ExposeConfiguration(
e => new SchemaExport(e).Drop(false, true)
);
_configStub1 = new PluginConfiguration()
{
Enabled = true,
Keys = "Name",
Value = "Fred",
PluginName = "red",
RuntimeId = 1
};
}
[Test]
[Explicit]
public void HighVolume_Saves_MultiManager_SameDataRecord_SameInstance_MultiThread()
{
Action action1 = () =>
{
var dal = new DataAccessManager();
for (int i = 0; i < 10000; i++)
{
dal.Begin();
dal.Current.Session.SaveOrUpdate(_configStub1);
dal.Current.Commit();
dal.End();
}
};
Action action2 = () =>
{
var dal = new DataAccessManager();
for (int i = 0; i < 10000; i++)
{
dal.Begin();
dal.Current.Session.SaveOrUpdate(_configStub1);
dal.Current.Commit();
dal.End();
}
};
var task1 = Task.Factory.StartNew(action1);
var task2 = Task.Factory.StartNew(action2);
task1.Wait();
task2.Wait();
}
テストで参照された DataAccess Manager は次のとおりです。
public class DataAccessManager : IDataAccessManager
{
private readonly ThreadLocal<ISessionManager> _current = new ThreadLocal<ISessionManager>();
public void Begin()
{
Current = new SessionManager();
}
public ISessionManager Current
{
get { return _current.Value; }
set { _current.Value = value; }
}
public void End(bool doComplete = true)
{
bool isActive = Current.Transaction != null && Current.Transaction.IsActive;
if (doComplete && isActive) Current.Commit();
else if (!doComplete && isActive) Current.Transaction.Rollback();
Current.Dispose();
}
}
SessionManager は次のとおりです。
public class SessionManager : ISessionManager
{
/// <summary>
/// Initializes a new instance of the <see cref="SessionManager"/> class.
/// </summary>
public SessionManager()
{
Session = ContextFactory.OpenSession();
Transaction = Session.BeginTransaction();
}
public ITransaction Transaction { get; private set; }
public ISession Session { get; private set; }
public void Commit()
{
try
{
Transaction.Commit();
}
catch (Exception ex)
{
Transaction.Rollback();
throw;
}
}
}