Проблема с использованием SQLite: memory: с NHibernate - PullRequest
32 голосов
/ 10 октября 2008

Я использую NHibernate для доступа к данным, и некоторое время я не использовал SQLite для локальных интеграционных тестов. Я использовал файл, но думал, что выберу опцию: memory: Когда я запускаю какой-либо из интеграционных тестов, кажется, что база данных создана (NHibernate выплевывает sql создания таблицы), но взаимодействие с базой данных вызывает ошибку.

Кто-нибудь получил NHibernate, работающий с базой данных в памяти? Это вообще возможно? Строка подключения, которую я использую, такова:

Data Source=:memory:;Version=3;New=True

Ответы [ 9 ]

41 голосов
/ 11 октября 2008

База данных памяти SQLite существует только до тех пор, пока соединение с ней остается открытым. Чтобы использовать его в модульных тестах с NHibernate:
1. Откройте ISession в начале вашего теста (возможно, с помощью метода [SetUp]).
2. Используйте соединение из этого сеанса в вызове SchemaExport.
3. Используйте тот же сеанс в своих тестах.
4. Закройте сеанс в конце теста (возможно, методом [TearDown]).

19 голосов
/ 22 марта 2013

Мне удалось использовать базу данных SQLite в памяти и избежать необходимости перестраивать схему для каждого теста с помощью поддержки SQLite для 'Shared Cache' , которая позволяет совместно использовать базу данных в памяти через соединения.

Я сделал следующее в AssemblyInitialize (я использую MSTest):

  • Сконфигурируйте NHibernate (свободно) для использования SQLite со следующей строкой подключения:

    FullUri=file:memorydb.db?mode=memory&cache=shared
    
  • Используйте эту конфигурацию для создания объекта hbm2ddl. SchemaExport и выполните его на отдельном соединении (но снова с той же строкой соединения).

  • Оставьте это соединение открытым и на него ссылается статическое поле до AssemblyCleanup , после чего оно закрывается и удаляется. Это связано с тем, что для SQLite требуется наличие хотя бы одного активного соединения в базе данных в памяти, чтобы знать, что оно все еще требуется, и избегать очистки.

Перед каждым тестом создается новый сеанс, и тест выполняется в транзакции, откат которой выполняется в конце.

Вот пример кода тестового уровня сборки:

[TestClass]
public static class SampleAssemblySetup
{
    private const string ConnectionString = "FullUri=file:memorydb.db?mode=memory&cache=shared";
    private static SQLiteConnection _connection;

    [AssemblyInitialize]
    public static void AssemblyInit(TestContext context)
    {
        var configuration = Fluently.Configure()
                                       .Database(SQLiteConfiguration.Standard.ConnectionString(ConnectionString))
                                       .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("MyMappingsAssembly")))
                                       .ExposeConfiguration(x => x.SetProperty("current_session_context_class", "call"))
                                       .BuildConfiguration();

        // Create the schema in the database
        // Because it's an in-memory database, we hold this connection open until all the tests are finished
        var schemaExport = new SchemaExport(configuration);
        _connection = new SQLiteConnection(ConnectionString);
        _connection.Open();
        schemaExport.Execute(false, true, false, _connection, null);
    }

    [AssemblyCleanup]
    public static void AssemblyTearDown()
    {
        if (_connection != null)
        {
            _connection.Dispose();
            _connection = null;
        }
    }
}

И базовый класс для каждого класса / прибора модульного тестирования:

public class TestBase
{
    [TestInitialize]
    public virtual void Initialize()
    {
        NHibernateBootstrapper.InitializeSession();
        var transaction = SessionFactory.Current.GetCurrentSession().BeginTransaction();
    }

    [TestCleanup]
    public virtual void Cleanup()
    {
        var currentSession = SessionFactory.Current.GetCurrentSession();
        if (currentSession.Transaction != null)
        {
            currentSession.Transaction.Rollback();
            currentSession.Close();
        }

        NHibernateBootstrapper.CleanupSession();
    }
}

Управление ресурсами может быть улучшено, я признаю, но это все-таки модульные тесты (рекомендуемые улучшения приветствуются!).

9 голосов
/ 13 октября 2008

Мы используем SQLite в памяти для всех наших тестов базы данных. Мы используем одно соединение ADO для тестов, которое повторно используется для всех сеансов NH, открытых одним и тем же тестом.

  1. Перед каждым тестом: создать соединение
  2. Создать схему для этого соединения
  3. Запустить тест. Одно и то же соединение используется для всех сеансов
  4. После теста: закрыть соединение

Это позволяет также запускать тесты с несколькими включенными сессиями. SessionFactory также создается один раз для всех тестов, поскольку чтение файлов сопоставления занимает довольно много времени.


Редактировать

Использование общего кэша

Поскольку System.Data.Sqlite 1.0.82 (или Sqlite 3.7.13 ), существует Shared Cache , который позволяет нескольким соединениям совместно использовать одни и те же данные, также для Базы данных в памяти . Это позволяет создавать базу данных в памяти в одном соединении и использовать ее в другом. (Я еще не пробовал, но теоретически это должно работать):

  • Измените строку подключения на file::memory:?cache=shared
  • Открыть соединение и создать схему
  • Держите это соединение открытым до конца теста
  • Пусть NH создаст другие соединения (нормальное поведение) во время теста.
8 голосов
/ 21 декабря 2010

У меня были похожие проблемы, которые продолжались даже после открытия ISession, как указано выше, и добавления «Pooling = True; Max Pool Size = 1» в строку подключения. Это помогло, но у меня все еще были случаи, когда соединение закрывалось во время теста (обычно сразу после совершения транзакции).

В конечном итоге мне удалось установить для свойства «connection.release_mode» значение «on_close» в конфигурации SessionFactory.

Моя конфигурация в файле app.config теперь выглядит следующим образом:

  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <reflection-optimizer use="true" />
    <session-factory>
      <property name="connection.connection_string_name">testSqlLiteDB</property>
      <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.release_mode">on_close</property>
      <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
      <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
      <property name="query.substitutions">true=1;false=0</property>
    </session-factory>
  </hibernate-configuration>

Надеюсь, это поможет!

0 голосов
/ 11 января 2019

Я получил ту же ошибку, когда забыл импортировать пакет SQLite Nuget.

0 голосов
/ 09 июня 2013

Просто хочу поблагодарить декатов. Я пытался решить эту проблему в течение пары месяцев, и все, что мне нужно было сделать, это добавить

FullUri=file:memorydb.db?mode=memory&cache=shared

к строке подключения в моем конфигурационном файле nhibernate. Кроме того, используется только NHibernate с * .hbm.xml, а не с FNH, и мне вообще не пришлось изменять мой код!

0 голосов
/ 29 января 2009

У меня много проблем с базой данных памяти SQLite. Поэтому теперь мы используем SQLite для работы с файлами на оперативном диске.

0 голосов
/ 22 октября 2008

Я делаю это с Rhino Commons . Если вы не хотите использовать Rhino Commons, вы можете изучить источник, посмотрите, как он это делает. Единственная проблема, с которой я столкнулся, заключается в том, что SQLite не поддерживает вложенные транзакции. Это заставило меня изменить код для поддержки интеграционного тестирования. Интеграционное тестирование с базой данных в памяти так здорово, что я решил, что это был справедливый компромисс.

0 голосов
/ 10 октября 2008

Просто дикая догадка, но вывод SQL из NHibernate с помощью команды не поддерживается sqlite?

Кроме того, что произойдет, если вы используете файл вместо памяти? (System.IO.Path.GetTempFileName () будет работать, я думаю ...)

...