我有一个ASP.NET MVC应用程序,我想为此编写一些压力测试,以了解从多个线程进行的并发数据库访问。我使用Parallel.ForEach()将其编写为单元测试,但由于大部分时间都遇到以下异常,因此无法使其正常工作:
There is already an open DataReader associated with this Command which must be closed first
所以我尽可能简化了测试,这就是
[Test]
public void Can_Access_DB_Concurrently()
{
Parallel.ForEach(Enumerable.Range(0, 9), x =>
{
try
{
var sessionBuilder = new HybridSessionBuilder();
var session = sessionBuilder.GetSession();
using (ITransaction transaction = session.BeginTransaction())
{
var job = session.Query<Job>().Where(y => y.Name == "TestProject").SingleOrDefault().Name;
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + ": Job name is " + job);
transaction.Commit();
}
}
catch (Exception e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + ": Exception: " + e.Message);
}
});
}
典型输出:
13: Exception: Object reference not set to an instance of an object.
16: Exception: There is already an open DataReader associated with this Command which must be closed first.
9: Exception: There is already an open DataReader associated with this Command which must be closed first.
16: Exception: There is already an open DataReader associated with this Command which must be closed first.
14: Exception: There is already an open DataReader associated with this Command which must be closed first.
HybridSessionBuilder
看起来像这样:public class HybridSessionBuilder : ISessionBuilder
{
private static ISessionFactory _sessionFactory;
private static ISession _currentSession;
public ISession GetSession()
{
ISessionFactory factory = getSessionFactory();
ISession session = getExistingOrNewSession(factory);
return session;
}
private ISessionFactory getSessionFactory()
{
lock (this)
{
if (_sessionFactory == null)
{
Configuration configuration = GetConfiguration();
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
public Configuration GetConfiguration()
{
string connectionString = WebConfigurationManager.ConnectionStrings["StagingDatabase"].ConnectionString;
Configuration configuration = new Configuration();
configuration = PostgreSQLConfiguration.PostgreSQL82
.ConnectionString(connectionString)
.Dialect("NHibernate.Dialect.PostgreSQL82Dialect")
.UseReflectionOptimizer()
.AdoNetBatchSize(50)
.ConfigureProperties(new Configuration());
configuration.AddMappingsFromAssembly(typeof(Job).Assembly);
Fluently.Configure(configuration);
return configuration;
}
private ISession getExistingOrNewSession(ISessionFactory factory)
{
{
if (HttpContext.Current != null)
{
ISession session = GetExistingWebSession();
if (session == null)
{
session = openSessionAndAddToContext(factory);
}
else if (!session.IsOpen)
{
session = openSessionAndAddToContext(factory);
}
return session;
}
if (_currentSession == null)
{
_currentSession = factory.OpenSession();
}
else if (!_currentSession.IsOpen)
{
_currentSession = factory.OpenSession();
}
}
return _currentSession;
}
public ISession GetExistingWebSession()
{
return HttpContext.Current.Items[GetType().FullName] as ISession;
}
private ISession openSessionAndAddToContext(ISessionFactory factory)
{
ISession session = factory.OpenSession();
HttpContext.Current.Items.Remove(GetType().FullName);
HttpContext.Current.Items.Add(GetType().FullName, session);
return session;
}
}
显然我在进行并发访问时出了点问题,但是我无法发现错误。感谢您的任何建议。
最佳答案
HybridSessionBuilder
将ISession存储在静态成员中,因此将其重新用于每个线程。修复测试的最简单解决方案是从static
删除_currentSession
关键字。然后,每个HybridSessionBuilder
实例将提供一个不同的ISession
。