使用 IsolationLevel.ReadUncommitted 时超时 SqlException

Timeout SqlException when using IsolationLevel.ReadUncommitted

我正在尝试编写一个集成测试来测试我的 FluentNHibernate 映射配置。测试的基本思路是插入一些数据,然后读回,并确保插入的数据与读回的数据匹配。我不想在每次测试 运行 时都将测试数据添加到数据库中,所以我尝试使用 t运行saction 插入数据,而 t运行saction 仍然打开,我尝试读回未提交的数据(使用单独的会话)。计划是,一旦做出断言,我将回滚围绕数据插入的 t运行saction,使我的数据库保持与测试 运行 之前相同的状态。不幸的是,我在尝试读取数据时遇到以下错误:

NHibernate.Exceptions.GenericADOException : could not load an entity: [CQRSTutorial.DAL.EventDescriptor#51ff4d15-ddbc-4157-8652-a7a10177a6e3][SQL: SELECT eventdescr0_.Id as Id1_2_0_, eventdescr0_.EventType as EventT2_2_0_, eventdescr0_.Data as Data3_2_0_ FROM Events eventdescr0_ WHERE eventdescr0_.Id=?]  ----> System.Data.SqlClient.SqlException : Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

我专门创建了一个测试来突出这个问题,如下所示:

[TestFixture, Category(TestConstants.Integration)]
public class TimeoutExpiredTest
{
    private ISession _writeSession;

    public static ISessionFactory CreateSessionFactory(IsolationLevel isolationLevel = IsolationLevel.Unspecified)
    {
        var msSqlConfiguration = MsSqlConfiguration
            .MsSql2012
            .IsolationLevel(isolationLevel)
            .ConnectionString(x => x.FromConnectionStringWithKey("CQRSTutorial"));

        var cfg = new CustomAutomappingConfiguration();
        return Fluently
            .Configure()
            .Database(msSqlConfiguration)
            .Mappings(m =>
            {
                m.AutoMappings.Add(
                    AutoMap.AssemblyOf<EventDescriptor>(cfg)
                        .UseOverridesFromAssemblyOf<EventDescriptorMapping>());
            })
            .BuildSessionFactory();
    }

    [SetUp]
    public void SetUp()
    {
        _writeSession = CreateSessionFactory().OpenSession();
        _writeSession.BeginTransaction();
    }

    [Test]
    public void InsertAndReadTabOpened()
    {
        var objectToStoreInDbTemporarily = GetObjectToStoreInDbTemporarily();

        _writeSession.SaveOrUpdate(objectToStoreInDbTemporarily);
        _writeSession.Flush(); // the data needs to go to the database (even if only temporarily), rather than just staying in an NHibernate cache.

        var readSession = CreateSessionFactory(IsolationLevel.ReadUncommitted).OpenSession();
        var retrievedEventDescriptor = readSession.Get<EventDescriptor>(objectToStoreInDbTemporarily.Id); // this line throws the exception after 30 seconds.

        // If previous line worked, I could then assert inserted values match retrieved values at this point.
    }

    [TearDown]
    public void TearDown()
    {
        _writeSession.Transaction?.Rollback();
    }

    public class CustomAutomappingConfiguration : DefaultAutomappingConfiguration
    {
        public override bool ShouldMap(Type type)
        {
            return type.Name == typeof(EventDescriptor).Name; // we're only mapping this class for now.
        }
    }

为简洁起见,我省略了创建对象的代码。最初的测试达到了它的目的——它迫使我必须为有问题的对象正确定义 NHibernate 映射。但是最初的测试是在每个测试 运行 中插入新行,这感觉很脏而且没有必要 - 因此这种 t运行saction / rollback 方法。

查看 SQL Profiler,问题似乎出在读取端 - 读取会话使用 IsolationLevel.ReadCommitted 而不是 IsolationLevel.ReadUncommitted。知道为什么读取会话不使用测试中指定的 IsolationLevel 吗?

啊,我刚刚又看了一眼,我想知道是不是因为readSession.Get()没有明确的交易。因此,NH 正在使用具有读提交隔离级别的隐式事务吗?也许将 readSession.Get() 包装在具有读取未提交隔离级别的显式事务中?

正如 David Osborne 所说,您也需要为阅读会话打开一个事务。

但是你所做的事情值得商榷。您依赖 SQL 服务器的内部实现细节来使您的测试成功。不能保证它目前有效。

更好地实施适当的测试设置和拆卸,进行必要的清理。您的测试将更加可靠。

另一种解决方案是共享两个会话之间的连接,方法是在打开会话时提供它,或者通过从另一个会话生成一个会话。这样他们的实体缓存将不会被共享,但他们将能够在不提交数据的情况下读取彼此的数据,因为他们将在同一个事务中。

另一种解决方案是使用单个事务范围进行测试,删除 NHibernate 事务。两个会话将在同一个事务中,并且能够读取彼此的数据。但这可能会促进事务分布式,然后您的设置将需要支持它。 (需要在测试主机上启用 MSDTC。)