未创建EntityFrameworkCore SQLite内存中数据库表

时间:2019-05-27 04:20:36

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

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

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

  

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

我已经了解到,EntityFrameworkCore内存数据库仅在打开连接存在的情况下才存在,因此我尝试显式创建一个var connection = new SqliteConnection("DataSource=:memory:");实例并将以下代码包装在{{ 1}}阻止并传递连接实例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); } 无法按预期创建架构。我不是将上面的代码作为运行集成测试的一般模式提供。

1 个答案:

答案 0 :(得分:1)

使用非共享的SQLite内存数据库

SQLite内存数据库默认情况下是瞬态的。作为documentation states

  

关闭数据库连接后,数据库将不复存在。每个:memory:数据库互不相同。

另一方面,

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对象可以连接到同一数据库。但是:

  

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

因此,仍然需要维护一个单独的开放连接对象,数据库才能在多个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实例。