C# NHibernate、多线程和共享对象更新冲突

C# NHibernate、多线程和共享对象更新冲突,c#,multithreading,nhibernate,fluent-nhibernate,C#,Multithreading,Nhibernate,Fluent Nhibernate,我知道ISession不是线程安全的,SessionFactory是线程安全的。因此,我已经包装并确认每个线程有一个会话 在以下情况下,我收到一个错误,我想知道这是否是不受支持的,或者我的ISession线程隔离仍然缺少一些东西 我正在进行NUnit测试。我有一个场景,其中我的实体作为字段变量被存根。我有一个运行两个并行任务的测试 •每个并行任务从同一SessionFactory创建自己的会话,并开始NHibernate事务。 •他们各自更新实体并对其执行保存或更新。 •然后提交并关闭事务。 每

我知道ISession不是线程安全的,SessionFactory是线程安全的。因此,我已经包装并确认每个线程有一个会话

在以下情况下,我收到一个错误,我想知道这是否是不受支持的,或者我的ISession线程隔离仍然缺少一些东西

我正在进行NUnit测试。我有一个场景,其中我的实体作为字段变量被存根。我有一个运行两个并行任务的测试

•每个并行任务从同一SessionFactory创建自己的会话,并开始NHibernate事务。
•他们各自更新实体并对其执行保存或更新。
•然后提交并关闭事务。
每个任务执行此操作的次数约为10000次

在测试过程中,我收到一条消息:

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特性来避免?或者这是不可能的,我必须确保这种情况(即我的实体对象一次没有被多个线程引用和更新)不会在我的代码中发生

我已经厌倦了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();
    }
private PluginConfiguration\u configStub1;
[设置]
公共作废设置()
{
新建FluentMapper().Configuration().ExposeConfiguration(
e=>newschemaexport(e).Drop(false,true)
);
_configStub1=新的插件配置()
{
启用=真,
Keys=“Name”,
Value=“Fred”,
PluginName=“红色”,
RuntimeId=1
};
}
[测试]
[明确]
public void HighVolume_save_multi-manager_SameDataRecord_SameInstance_multi-thread()
{
动作1=()=>
{
var dal=新的DataAccessManager();
对于(int i=0;i<10000;i++)
{
dal.Begin();
dal.Current.Session.SaveOrUpdate(_configStub1);
dal.Current.Commit();
dal.End();
}
};
动作2=()=>
{
var dal=新的DataAccessManager();
对于(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.等待();
任务2.等待();
}
测试中引用的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();
    }
}
公共类DataAccessManager:IDataAccessManager
{
private readonly ThreadLocal_current=new ThreadLocal();
公共空间开始()
{
当前=新会话管理器();
}
公共ISessionManager当前版本
{
获取{return}current.Value;}
设置{u current.Value=Value;}
}
公共作废结束(bool doccomplete=true)
{
bool isActive=Current.Transaction!=null&&Current.Transaction.isActive;
如果(doComplete&&isActive)当前.Commit();
如果(!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;
        }
    }
}
公共类会话管理器:ISessionManager
{
/// 
///初始化类的新实例。
/// 
公共会话管理器()
{
Session=ContextFactory.OpenSession();
事务=Session.BeginTransaction();
}
公共ITransaction事务{get;private set;}
公共ISession会话{get;private set;}
公共无效提交()
{
尝试
{
Commit();
}
捕获(例外情况除外)
{
Transaction.Rollback();
投掷;
}
}
}

与会话中当前活动的实体对象(即会话已知)交互应视为与会话交互。由于会话对于从不同线程并发使用是不安全的,因此对会话已知的实体对象的访问也是不安全的

这是因为与实体实例交互可能会导致会话延迟加载某些数据,并且在调用Save()(或类似方法)期间,NHibernate将改变对象以设置id(取决于choosen标识分配策略)


上述规定适用于一般情况。在更狭隘的场景中,安全地进行操作是可能的,但我认为,在引入这种复杂性之前,您应该确实确定您需要这样的东西。

与会话中当前活动的实体对象(即会话已知)交互应视为与会话交互。由于会话对于从不同线程并发使用是不安全的,因此对会话已知的实体对象的访问也是不安全的

这是因为与实体实例交互可能会导致会话延迟加载某些数据,并且在调用Save()(或类似方法)期间,NHibernate将改变对象以设置id(取决于choosen标识分配策略)


上述规定适用于一般情况。在更狭隘的情况下,可能安全地做到这一点,但我认为,在引入复杂性之前,你应该确信你确实需要这样的东西。