【问题标题】:Problem using SQLite :memory: with NHibernate使用 SQLite 的问题:内存:与 NHibernate
【发布时间】:2010-09-16 09:35:36
【问题描述】:

我将 NHibernate 用于我的数据访问,并且有一段时间没有使用 SQLite 进行本地集成测试。我一直在使用文件,但我想我会退出 :memory: 选项。当我启动任何集成测试时,数据库似乎已创建(NHibernate 吐出表创建 sql),但与数据库交互会导致错误。

有没有人让 NHibernate 使用内存数据库?甚至可能吗?我使用的连接字符串是这样的:

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

【问题讨论】:

  • 一段时间以来还有另一个解决方案。请参阅我的答案中的附加块。

标签: c# nhibernate sqlite orm integration-testing


【解决方案1】:

一个 SQLite 内存数据库只有在与它的连接保持打开时才存在。在 NHibernate 的单元测试中使用它:
1. 在测试开始时打开一个 ISession(可能在 [SetUp] 方法中)。
2. 在 SchemaExport 调用中使用来自该会话的连接。
3. 在您的测试中使用相同的会话。
4. 在测试结束时关闭会话(可能在 [TearDown] 方法中)。

【讨论】:

【解决方案2】:

我能够使用 SQLite 内存数据库,并避免使用 SQLite 的 support for 'Shared Cache' 为每个测试重建架构,这允许跨连接共享内存数据库。

我在 AssemblyInitialize 中做了以下操作(我正在使用 MSTest):

  • 配置 NHibernate (Fluently) 以使用带有以下连接字符串的 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();
    }
}

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

【讨论】:

  • 完美!这是最好的解决方案,其他解决方案通常基于旧版本的 sqlite,它们不提供在连接池活动的情况下使用命名 ("file:xyz.db?mode=memory") 内存中的 sqlite dbs 的可能性。
  • 请注意,在更高版本的 sqlite 中,您可能需要为连接字符串执行类似的操作(这适用于 Loquacious API):config.DataBaseIntegration(c => { /*...*/ c.ConnectionString = "DataSource=file:memdb1?mode=memory&cache=shared" }) -- 参见 stackoverflow.com/a/12324672/489116 和 cmets,以及 @987654323 @.
【解决方案3】:

我们在内存中使用 SQLite 进行所有数据库测试。我们为测试使用单个 ADO 连接,该连接被同一测试打开的所有 NH 会话重用。

  1. 每次测试之前:创建连接
  2. 在此连接上创建架构
  3. 运行测试。所有会话都使用相同的连接
  4. 测试后:关闭连接

这还允许运行包含多个会话的测试。 SessionFactory 也为所有测试创建一次,因为读取映射文件需要相当长的时间。


编辑

使用共享缓存

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

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

【讨论】:

  • 您是使用 OpenSession 重载来提供连接,还是有更巧妙的方式使用相同的连接?
  • 我使用 OpenSession(IDbConnection),并从 sessionFactory.ConnectionProvider.GetConnection() 获取一次连接。我可能可以实现我自己的 ConnectionProvider,那么这只是一个配置问题。但是直到现在我都没有时间这样做。
【解决方案4】:

即使在如上所述打开 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 只能获得一个连接,但可能需要一个额外的连接才能从数据库中获取下一个 HiLo-Range。对于较新版本的 sqlite,更好的解决方案是使用提供 link 的解决方案。
【解决方案5】:

我已经解决了 SQLite 内存数据库的很多问题。 所以现在我们使用 SQLite 处理 ramdrive 磁盘上的文件。

【讨论】:

  • 这在过去可能是一个解决方案,但对于新版本的 sqlite 来说是不必要的,因为它们提供了使用数据库共享范围的能力。这更容易使用并且工具密集度更低。有关最新的解决方案,请参阅以下answer
【解决方案6】:

只是一个疯狂的猜测,但是 NHibernate 使用 sqlite 不支持的命令输出的 sql 吗?

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

【讨论】:

    【解决方案7】:

    我正在使用Rhino Commons。如果您不想使用 Rhino Commons,您可以研究源代码,看看它是如何做到的。我遇到的唯一问题是 SQLite 不支持嵌套事务。这迫使我更改代码以支持集成测试。内存数据库的集成测试太棒了,我认为这是一个公平的折衷方案。

    【讨论】:

      【解决方案8】:

      只想感谢decates。几个月来一直在尝试解决这个问题,我所要做的就是添加

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

      到我的休眠配置文件中的连接字符串。也只使用 NHibernate 和 *.hbm.xml 而不是 FNH 并且根本不需要修改我的代码!

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-07
          • 1970-01-01
          • 2011-08-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多