我可以刷新我的NHibernate会话并在不提交事务的情况下获得新会话吗?

时间:2008-11-01 18:41:45

标签: nhibernate testing castle-activerecord database-testing

我正在使用Castle ActiveRecord进行持久化,我正在尝试为持久性测试编写一个基类,它将执行以下操作:

  • 为每个测试用例打开一个事务,并在测试用例结束时将其回滚,这样我就可以为每个测试用例获得一个干净的数据库,而不必为每个测试用例重建模式。
  • 提供刷新我的NHibernate会话并在测试过程中获得一个新会话的能力,这样我就知道我的持久化操作确实击中了数据库,而不仅仅是NHibernate会话。

为了证明我的基类(ARTestBase)正在运行,我已经提出了以下样本测试。

[TestFixture]
public class ARTestBaseTest : ARTestBase
{
    [Test]
    public void object_created_in_this_test_should_not_get_committed_to_db()
    {
        ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});

        Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
    }

    [Test]
    public void object_created_in_previous_test_should_not_have_been_committed_to_db()
    {
        ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});

        Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
    }

    [Test]
    public void calling_flush_should_make_nhibernate_retrieve_fresh_objects()
    {
        var savedEntity = new Entity {Name = "test"};
        ActiveRecordMediator<Entity>.Save(savedEntity);
        Flush();
        // Could use FindOne, but then this test would fail if the transactions aren't being rolled back
        foreach (var entity in ActiveRecordMediator<Entity>.FindAll())
        {
            Assert.That(entity, Is.Not.SameAs(savedEntity));
        }
    }
}

这是我在基类的最大努力。它正确实现了Flush(),因此第三个测试用例通过了。但是它不会回滚事务,因此第二次测试失败。

public class ARTestBase
{
    private SessionScope sessionScope;
    private TransactionScope transactionScope;

    [TestFixtureSetUp]
    public void InitialiseAR()
    {
        ActiveRecordStarter.ResetInitializationFlag();
        ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
        ActiveRecordStarter.CreateSchema();
    }

    [SetUp]
    public virtual void SetUp()
    {
        transactionScope = new TransactionScope(OnDispose.Rollback);
        sessionScope = new SessionScope();
    }

    [TearDown]
    public virtual void TearDown()
    {
        sessionScope.Dispose();
        transactionScope.Dispose();
    }

    protected void Flush()
    {
        sessionScope.Dispose();
        sessionScope = new SessionScope();
    }

    [TestFixtureTearDown]
    public virtual void TestFixtureTearDown()
    {
        SQLiteProvider.ExplicitlyDestroyConnection();
    }
}

请注意,我正在使用带有内存数据库的自定义SQLite提供程序。我的自定义提供程序取自this blog post,始终保持连接处于打开状态以维护架构。删除它并使用常规SQL Server数据库不会改变行为。

有没有办法实现所需的行为?

2 个答案:

答案 0 :(得分:1)

对ActiveRecord不太确定,但在NHibernate中,一个事务属于一个会话,而不是另一个事务。

如果您经常使用ADO.Net,这将更有意义,因为您需要创建一个IDbTransaction来使用连接。 ActiveRecord的TransactionScope(和NHibnerate的ITransaction)基本上包裹IDbTransaction,因此您需要在SessionScope之前创建TransactionScope

您可能也会发现(取决于您使用的是NHibernate 1.2 GA或NHibernate 2. *,以及FlushMode SessionScope}您对FindAll()的调用)可能导致会话无论如何都会刷新,因为NHibernate会意识到它无法在不调用Save的最后一次调用的情况下检索正确的数据。

这一切都说完了,您是否尝试过使用SessionScope.Flush()而不是创建新的SessionScope

答案 1 :(得分:0)

使用SessionScope.Flush()使我的第三次测试失败。据我了解,Flush()执行SQL将我的记录推送到数据库中,但不会从会话中逐出对象。这符合您对FindAll()导致刷新的看法。

我真正想要的是SessionScope.Flush()(将DB的状态与会话同步)加上SessionScope.EvictAll()(以确保我在后续查询中获得新鲜对象)。我的new SessionScope()试图模拟EvictAll()

你对围绕交易而不是反过来的会话的评论确实给了我一个想法。我不确定在刷新的SessionScope内的TransactionScope内创建一个新的SessionScope是多么犹豫,并期望它参与交易,但似乎有效:< / p>

public abstract class ARTestBase
{
    private SessionScope sessionScope;
    private TransactionScope transactionScope;
    private bool reverse;
    private IList<SessionScope> undisposedScopes;

    [TestFixtureSetUp]
    public void InitialiseAR()
    {
        ActiveRecordStarter.ResetInitializationFlag();
        ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
        ActiveRecordStarter.CreateSchema();
        InitialiseIoC();
        undisposedScopes = new List<SessionScope>();
    }

    [SetUp]
    public virtual void SetUp()
    {
        sessionScope = new SessionScope();
        transactionScope = new TransactionScope(OnDispose.Rollback);
        transactionScope.VoteRollBack();
        base.CreateInstanceUnderTest();
        reverse = false;
    }

    [TearDown]
    public virtual void TearDown()
    {
        if (reverse)
        {
            sessionScope.Dispose();
            transactionScope.Dispose();
        }
        else
        {
            transactionScope.Dispose();
            sessionScope.Dispose();
        }
    }

    [TestFixtureTearDown]
    public virtual void TestFixtureTearDown()
    {
        foreach (var scope in undisposedScopes)
        {
            scope.Dispose();
        }
        SQLiteProvider.ExplicitlyDestroyConnection();
    }

    protected void Flush()
    {
        reverse = true;
        sessionScope.Flush();
        undisposedScopes.Add(sessionScope);
        sessionScope = new SessionScope();
    }
}

进一步考虑,这不允许您在每个测试用例中多次刷新。我想我可以通过更仔细地跟踪范围来处理这个问题。我可能会稍后调查一下。