使用IsolationLevel.ReadUncommitted

时间:2017-06-29 23:19:27

标签: c# sql-server nhibernate fluent-nhibernate

我正在尝试编写一个测试我的FluentNHibernate映射配置的集成测试。测试的基本思想是插入一些数据,然后将其读回,并确保插入的数据与读回的数据相匹配。我不希望在每次测试运行时都将测试数据添加到数据库中,因此我尝试使用事务插入数据,并且当该事务仍处于打开状态时,我尝试读取未提交的数据(使用单独的会话)。计划是,一旦断言完成,我将围绕插入数据回滚事务,使我的数据库处于测试运行之前的状态。不幸的是,我在尝试读取数据时遇到以下错误:

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映射。但是最初的测试是在每次测试运行时插入新行,感觉很脏且不必要 - 因此这种事务/回滚方法。

查看SQL事件探查器,似乎问题出在Read端 - Read会话使用的是IsolationLevel.ReadCommitted而不是IsolationLevel.ReadUncommitted。任何想法为什么读取会话不会使用测试中指定的IsolationLevel?

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

正如David Osborne所说,你也需要为你的阅读课程开设一个交易。

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

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

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

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