我可以刷新 NHibernate 会话并获取新会话而不提交事务吗?

发布于 2024-07-07 21:46:55 字数 2711 浏览 4 评论 0原文

我正在使用 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 提供程序。 我的自定义提供程序,取自这篇博文,始终保持连接打开以维护架构。 删除它并使用常规 SQL Server 数据库不会改变行为。

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

I'm using Castle ActiveRecord for persistence, and I'm trying to write a base class for my persistence tests which will do the following:

  • Open a transaction for each test case and roll it back at the end of the test case, so that I get a clean DB for each test case without me having to rebuild the schema for each test case.
  • Provide the ability to flush my NHibernate session and get a new one in the middle of a test, so that I know that my persistence operations have really hit the DB rather than just the NHibernate session.

In order to prove that my base class (ARTestBase) is working, I've come up with the following sample tests.

[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));
        }
    }
}

Here is my best effort at the base class. It correctly implements Flush(), so the third test case passes. However it does not rollback the transactions, so the second test fails.

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();
    }
}

Note that I'm using a custom SQLite provider with an in-memory database. My custom provider, taken from this blog post, keeps the connection open at all times to maintain the schema. Removing this and using a regular SQL Server database doesn't change the behaviour.

Is there a way to acheive the required behaviour?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

剑心龙吟 2024-07-14 21:46:55

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

如果您经常使用 ADO.Net,这会更有意义,因为要创建您需要使用连接的 IDbTransaction。 ActiveRecord 的 TransactionScope (和 NThibnerate 的 ITransaction)本质上包装了一个 IDbTransaction,因此您需要在 SessionScope 之前创建 SessionScope。代码>事务范围。

您可能还会发现(取决于您使用的是 NHibernate 1.2 GA 还是 NHibernate 2.*,以及 SessionScopeFlushMode),您对 FindAll() 可能会导致会话刷新,因为 NHibernate 会意识到,如果不执行最后一次对 Save 的调用,它就无法检索正确的数据。

说了这么多,您是否尝试过使用 SessionScope.Flush() 而不是创建新的 SessionScope ?

Not too sure about ActiveRecord, but in NHibernate a transaction belongs to a session, not the otherway round.

If you've used ADO.Net a lot, this will make more sense, as to create an IDbTransaction you need to use the connection. ActiveRecord's TransactionScope (and NHibnerate's ITransaction) essentially wrap an IDbTransaction, so you need to create the SessionScope before the TransactionScope.

What you might also find (depending on if you're using NHibernate 1.2 GA or NHibernate 2.*, and what FlushMode your SessionScope has) is that your call to FindAll() may cause the session to flush anyway, as NHibernate will realise that it can't retrieve the correct data without actioning the last call to Save.

This all said and done, have you tried using SessionScope.Flush() instead of creating a new SessionScope?

清君侧 2024-07-14 21:46:55

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

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

您对包含交易的会议的评论,而不是相反,确实给了我一个想法。 我不确定在刷新的 SessionScope 内的 TransactionScope 中创建一个新的 SessionScope 是否合理,并期望它参与事务,但它似乎有效:

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();
    }
}

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

Using SessionScope.Flush() makes my third test fail. As I understand it, Flush() executes the SQL to push my records into the DB, but does not evict objects from the session. That fits with what you say about FindAll() causing a flush.

What I really want is SessionScope.Flush() (to synchronise state of DB with session) plus SessionScope.EvictAll() (to ensure I get fresh objects in subsequent queries). My new SessionScope() was an attempt to simulate EvictAll().

Your comments about the session enclosing the transaction rather than the other way round did give me an idea. I'm not sure how kosher it is to create a new SessionScope inside a TransactionScope inside a flushed SessionScope, and expect it to participate in the transaction, but it seems to work:

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();
    }
}

On further thought, this won't allow you to flush more than once in each test case. I think I can handle that by tracking the scopes more carefully. I might look into it later.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文