使用SQLite :memory: 和 NHibernate 存在问题

34

我使用NHibernate进行数据访问,最近我一直在使用SQLite进行本地集成测试。我一直使用文件,但是我想尝试使用`:memory:`选项。当我启动任何集成测试时,数据库似乎已经被创建(NHibernate输出了表格创建SQL),但是与数据库交互会导致错误。

有人曾经成功地使用内存数据库与NHibernate配合吗?这是否可能?我使用的连接字符串如下:

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

有另一种解决方案已经有一段时间了。请查看我的答案中的附加块。 - Stefan Steinegger
9个回答

41

SQLite内存数据库只在连接保持打开状态时存在。为了在NHibernate的单元测试中使用它,需要按照以下步骤:
1. 在测试开始时(可能是[SetUp]方法中),打开一个ISession。
2. 在SchemaExport调用中使用该会话的连接。
3. 在测试中使用同一个会话。
4. 在测试结束时(可能是[TearDown]方法中),关闭会话。


啊,那很有道理!我会试一试。 - Chris Canal
1
请查看以下实现DriverConnectionProvider的示例: http://notepad2.wordpress.com/2008/05/19/unit-testing-castle-active-record-using-sqlite-in-memory-database/ - smoothdeveloper
2
顺便说一下,我在调用Flush后无法保持与NHibernate的连接。在http://bit.ly/23CRTT找到了答案。 - Jon Adams

21
我能够使用SQLite内存数据库,避免为每个测试重新构建模式,这是通过使用SQLite的支持“共享缓存”实现的,它允许在内存数据库之间共享连接。
我在AssemblyInitialize(我正在使用MSTest)中执行了以下操作:
  • Configure NHibernate (Fluently) to use SQLite with the following connection string:

    FullUri=file:memorydb.db?mode=memory&cache=shared
    
  • Use that configuration to create a hbm2ddl.SchemaExport object, and execute it on a separate connection (but with that same connection string again).

  • Leave that connection open, and referenced by a static field, until AssemblyCleanup, at which point it is closed and disposed of. This is because SQLite needs at least one active connection to be held on the in-memory database to know it's still required and avoid tidying up.
在每次测试运行之前,都会创建一个新的会话,并在事务中运行该测试,该事务在结束时将被回滚。
以下是测试组件级别代码的示例:
[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();
    }
}

我承认资源管理可能需要改进,但这毕竟只是单元测试(欢迎提出改进建议!)。


太好了!这是最佳解决方案,其他解决方案通常基于旧版本的sqlite,无法使用带有连接池的命名(“file:xyz.db?mode = memory”)内存sqlite数据库。 - hessenmob82
请注意,在较新版本的sqlite中,您可能需要对连接字符串执行类似以下操作(并且这适用于Loquacious API):config.DataBaseIntegration(c => { /*...*/ c.ConnectionString = "DataSource=file:memdb1?mode=memory&cache=shared" }) -- 请参见 https://dev59.com/a2fWa4cB1Zd3GeqPdx5n#12324672 和评论,以及 https://forums.devart.com/viewtopic.php?t=26312#p119530。 - S'pht'Kr

9
我们在所有数据库测试中使用内存中的SQLite。我们为测试使用单个ADO连接,该连接在同一测试中打开的所有NH会话中重复使用。
  1. 每次测试之前:创建连接
  2. 在此连接上创建模式
  3. 运行测试。相同的连接用于所有会话
  4. 测试完成后:关闭连接
这也允许在包括多个会话的情况下运行测试。SessionFactory也为所有测试创建一次,因为读取映射文件需要相当长的时间。

编辑

共享缓存的使用

自System.Data.Sqlite 1.0.82(或Sqlite 3.7.13)以来,有一个共享缓存,允许多个连接共享相同的数据,也适用于内存数据库。这允许在一个连接中创建内存数据库,并在另一个连接中使用它。(我还没有尝试过,但理论上应该可以):

  • 将连接字符串更改为file::memory:?cache=shared
  • 打开一个连接并创建模式
  • 保持此连接打开直到测试结束
  • 让NH在测试期间创建其他连接(正常行为)。

你是否使用OpenSession重载来提供连接,还是有更巧妙的方法来使用同一连接? - Graham Ambrose
我使用OpenSession(IDbConnection),并从sessionFactory.ConnectionProvider.GetConnection()获取连接。我可能可以实现自己的ConnectionProvider,然后只需要进行配置即可。但是直到现在我还没有时间去做这件事。 - Stefan Steinegger

8

我曾经遇到过类似的问题,即使按照上面所述的方法打开ISession,并在我的连接字符串中添加了“Pooling=True;Max Pool Size=1”,问题仍然存在。虽然这些措施有所帮助,但我仍然遇到了一些情况,在测试期间连接会关闭(通常是在提交事务后立即发生)。

最终对我有用的是在我的SessionFactory配置中将属性“connection.release_mode”设置为“on_close”。

现在我的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>

希望能帮到你!

尝试了这个解决方案,但在使用HiLo Id-Generators时它有一个严重的缺陷。当插入大量新的数据库条目时,这个解决方案会崩溃。原因是nhibernate只能获取一个连接,但可能需要额外的连接来从DB获取下一个HiLo-Range。对于较新版本的sqlite,更好的解决方案是使用decates提供的解决方案[链接](https://dev59.com/1HVC5IYBdhLWcg3wxEJ1#15574248)。 - hessenmob82

1

我使用SQLite内存数据库遇到了很多问题。现在我们正在使用在ramdrive磁盘上处理文件的SQLite。


1
这可能是过去的解决方案,但在较新版本的sqlite中是不必要的,因为它们提供了使用数据库共享范围的能力。这样使用起来更加容易,工具也更少。有关最新的解决方案,请参见以下[答案](https://dev59.com/1HVC5IYBdhLWcg3wxEJ1#15574248)。 - hessenmob82

0

我遇到了同样的错误,当我忘记导入SQLite Nuget包时。


0

只是一个猜测,NHibernate输出的SQL是否使用了SQLite不支持的命令?

另外,如果使用文件而不是内存会发生什么?(我认为System.IO.Path.GetTempFileName()可以工作...)


0

我正在使用Rhino Commons。如果你不想使用Rhino Commons,你可以研究源代码看它是如何实现的。我遇到的唯一问题是SQLite不支持嵌套事务。这迫使我改变了我的代码以支持集成测试。使用内存数据库进行集成测试非常棒,我认为这是一个公平的妥协。


0

只是想感谢decates。我已经试图解决这个问题几个月了,而我所需要做的就是添加

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

在我的NHibernate配置文件中添加连接字符串。同时,只使用*.hbm.xml的NHibernate,而不是FNH,并且几乎没有修改我的代码!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接