未创建 EntityFrameworkCore SQLite 内存数据库表

Bal*_*tar 7 c# sqlite integration-testing in-memory-database entity-framework-core-2.2

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

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

确保上下文的数据库存在。如果存在,则不执行任何操作。如果它不存在,则创建数据库及其所有模式。如果数据库存在,则不会采取任何措施来确保它与此上下文的模型兼容。

我已经读到EntityFrameworkCore内存中的数据库只存在于打开的连接时才存在,所以我尝试显式创建一个var connection = new SqliteConnection("DataSource=:memory:");实例并将下面的代码包装在一个using(connection) {}块中并传递连接实例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);
    }
Run Code Online (Sandbox Code Playgroud)

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

div*_*ega 20

使用非共享 SQLite 内存数据库

SQLite 内存数据库默认是瞬态的。正如文档所述

一旦数据库连接关闭,数据库就不再存在。每个 :memory: 数据库都与其他数据库不同。

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

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

例如:

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

  services.AddDbContext<MyContext>(options =>
  {
    options.UseSqlite(keepAliveConnection);
  });
Run Code Online (Sandbox Code Playgroud)

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

顺便说一下,这是EF Core 文档中当前使用的关于如何使用 SQLite 内存数据库进行测试的方法

使用共享的 SQLite 内存数据库

SQLite 还支持命名共享内存数据库。通过使用相同的连接字符串,多个SqliteConnection对象可以连接到同一个数据库。然而:

当与数据库的最后一个连接关闭时,数据库将被自动删除并回收内存。

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

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

  services.AddDbContext<MyContext>(options =>
  {
    options.UseSqlite(connectionString);
  });
Run Code Online (Sandbox Code Playgroud)

请注意,这种方法不限于单个线程,因为每个线程DbContext都有自己的SqliteConnection.

  • 我确实仔细阅读了你的问题。我的那部分答案适用于找到此答案的其他所有人,因为他们正在尝试与您做类似的事情(但不完全相同)。使用 SQLite 内存数据库有一个值得指出的特定陷阱。 (3认同)
  • 请注意,“Mode=Memory”在 2.1 上不起作用,但在 3.1 中已修复。我让它与这个连接字符串“DataSource=file::memory:?cache=shared”一起工作 (3认同)