Могу ли я очистить сеанс NHibernate и получить новый сеанс без фиксации транзакции? - PullRequest
2 голосов
/ 01 ноября 2008

Я использую 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 не изменяет поведение.

Есть ли способ добиться необходимого поведения?

Ответы [ 2 ]

1 голос
/ 03 ноября 2008

Не слишком уверен насчет 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?

0 голосов
/ 03 ноября 2008

Использование 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();
    }
}

Если подумать, это не позволит вам сбрасывать более одного раза в каждом тестовом случае. Я думаю, что справлюсь с этим, отслеживая области более тщательно. Я мог бы изучить это позже.

...