c# - EntityFrameworkCore SQLite 内存数据库表未创建
问题描述
对于集成测试,我使用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);
}
请注意,在这篇文章中,我只是在问为什么不DbContext.Database.EnsureCreated();
按预期创建架构的问题。我没有将上述代码作为运行集成测试的一般模式。
解决方案
使用非共享 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);
});
请注意,这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);
});
请注意,这种方法不限于单个线程,因为每个线程DbContext
都有自己的SqliteConnection
.
推荐阅读
- vb.net - 下载源代码,然后从 ID 中提取值
- cassandra - 如何为 STCS 压缩策略配置“频率”和“最大 sstable 大小”?
- php - 如何在 wordpress 上显示我的测试结果?
- swift - UICollection 视图与 TableView 错误索引超出范围为什么
- asp.net-core - PWA ServiceWorker 不适用于 Asp.net core Spa 扩展
- python - 使用 Docker 复制源文件时的最佳实践
- java - maven 避免包含依赖项
- ios - 将两个数组匹配为一个数组。不通过 for 循环
- android - Android从拖动主要内容布局向上滑动底部工作表
- spring-security-oauth2 - WebLogic HTTP 401 问题上的 Spring Security