对于集成测试,我使用的是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);
}
无法按预期创建架构。我不是将上面的代码作为运行集成测试的一般模式提供。
答案 0 :(得分:1)
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还支持命名为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
实例。