我知道 ISession 不是线程安全的,而 SessionFactory 是线程安全的。因此,我已经打包并确认每个线程都有一个会话。
在以下情况下,我收到一个错误,想知道这是否不受支持,或者我的 ISession 线程隔离仍然缺少某些东西。
我正在运行 NUnit 测试。我有一个场景,我的实体作为字段变量被存根。我有一个运行 2 个并行任务的测试。
• 每个并行任务从同一个SessionFactory 创建它自己的会话并开始一个NHibernate Transaction。
• 它们各自更新实体并对其执行SaveOrUpdate。
• 然后提交并关闭事务。
每个任务执行大约 10k 次。
在此测试期间,我收到一条消息:
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();
}
测试中引用的DataAccessManager如下:
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;
}
}
}