c# - EntityFrameworkCore SQLite 内存数据库表未创建

标签 c# sqlite integration-testing in-memory-database entity-framework-core-2.2

对于集成测试,我使用 EntityFrameworkCore SQLite 内存数据库并根据 Microsoft 文档创建其架构,但是当我尝试播种数据时,会引发异常该表不存在。

DbContext.Database.EnsureCreated(); 的鼠标悬停文档:

Ensure that the database for the context exists. If it exists, no action is taken. If it does not exist then the database and all its schema are created. If the database exists, then no action is made to ensure it is compatible with the model for this context.

我读到,EntityFrameworkCore 内存数据库仅在存在打开的连接时才存在,因此我尝试显式创建 var connection = new SqliteConnection("DataSource= :memory:"); 实例并将以下代码包装在 using(connection) {} block 中并传递连接实例 options.UseSqlite(connection);,但是DbContext.Database.EnsureCreated();仍然没有创建任何数据库对象

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<Startup>
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return WebHost.CreateDefaultBuilder()
            .UseStartup<Startup>();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
      using (var connection = new SqliteConnection("DataSource=MySharedInMemoryDb;mode=memory;cache=shared"))
      {
          connection.Open();
          builder.ConfigureServices(services =>
          {
              var serviceProvider = new ServiceCollection()
                  .AddEntityFrameworkSqlite()
                  .BuildServiceProvider();

              services.AddDbContext<MyDbContext>(options =>
              {
                  options.UseSqlite(connection);
                  options.UseInternalServiceProvider(serviceProvider);
              });

              var contextServiceProvider = services.BuildServiceProvider();

              // we need a scope to obtain a reference to the database contexts
              using (var scope = contextServiceProvider.CreateScope())
              {
                  var scopedProvider = scope.ServiceProvider;

                  var logger = scopedProvider.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                  using (var myDb = scopedProvider.GetRequiredService<MyDbContext>())
                  {
                      // DEBUG CODE
                      // this returns script to create db objects as expected
                      // proving that MyDbContext is setup correctly
                      var script = myDb.Database.GenerateCreateScript();
                      // DEBUG CODE

                      // this does not create the db objects ( tables etc )
                      // this is not as expected and contrary to ms docs
                      var result = myDb.Database.EnsureCreated();

                      try
                      {
                          SeedData.PopulateTestData(myDb);
                      }
                      catch (Exception e)
                      {
                          // exception is thrown that tables don't exist
                          logger.LogError(e, $"SeedData.PopulateTestData(myDb) threw exception=[{e.Message}]");
                      }
                  }
              }
          });
        }
        builder.UseContentRoot(".");
        base.ConfigureWebHost(builder);
    }

请注意,在这篇文章中我只是问为什么 DbContext.Database.EnsureCreated(); 没有按预期创建架构。我不会将上述代码作为运行集成测试的通用模式来呈现。

最佳答案

使用非共享 SQLite 内存数据库

SQLite 内存数据库默认是 transient 的。作为documentation states :

The database ceases to exist as soon as the database connection is closed. Every :memory: database is distinct from every other.

另一方面,EF Core 的 DbContext 始终自动打开和关闭与数据库的连接,除非您传递已打开的连接。

因此,为了在 EF Core 中的多个调用中使用相同的 SQLite 内存数据库,您需要单独创建一个 SqliteConnection 对象,然后将其传递给每个 DbContext.

例如:

  var keepAliveConnection = new SqliteConnection("DataSource=:memory:");
  keepAliveConnection.Open();

  services.AddDbContext<MyContext>(options =>
  {
    options.UseSqlite(keepAliveConnection);
  });

请注意,SqliteConnection 并不是真正的线程安全,因此此方法仅适用于单线程场景。任何时候你想要一个可以被多个线程访问的共享数据库(例如,在 ASP.NET Core 应用程序中,为多个请求提供服务),你应该考虑使用磁盘数据库。

顺便说一句,这是EF Core documentation on how to use SQLite in-memory databases for testing中当前使用的方法。 .

使用共享 SQLite 内存数据库

SQLite 还支持命名 shared in-memory databases 。通过使用相同的连接字符串,多个 SqliteConnection 对象可以连接到同一个数据库。但是:

The database is automatically deleted and memory is reclaimed when the last connection to the database closes.

因此仍然需要维护一个单独的打开连接对象,以便数据库可以在多个 EF Core 调用之间使用。例如:

  var connectionString = "DataSource=myshareddb;mode=memory;cache=shared";
  var keepAliveConnection = new SqliteConnection(connectionString);
  keepAliveConnection.Open();

  services.AddDbContext<MyContext>(options =>
  {
    options.UseSqlite(connectionString);
  });

请注意,此方法不限于单个线程,因为每个 DbContext 都有自己的 SqliteConnection 实例。

关于c# - EntityFrameworkCore SQLite 内存数据库表未创建,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56319638/

相关文章:

c# - 有没有办法将 JSON 对象列表从 JS 传递到 C#?

android - 尝试重新打开一个已经关闭的对象:SQLiteQuery

android - 安装 Android SQLite Asset Helper

javascript - Jest 比较具有任意属性值的对象

unit-testing - 单元测试?集成测试?回归测试?验收测试?

java - 使用 JPA 和 Spring 进行集成测试

c# - 如何使异步任务返回 true 或 false?

c# - 存储父子关系

c# - 使用通配符或递归进行 App.config 运行时探测

ios - 本地 SQLite 数据库在多大程度上适合 iOS 应用程序?