EntityFrameworkCore Базы данных SQLite в памяти не создаются
Для интеграционных тестов я использую EntityFrameworkCore
SQLite
in-memory db и создание его схемы в соответствии с документами Microsoft, но когда я пытаюсь заполнить данные, возникает исключение, что таблицы не существуют.
Мышиные документы для DbContext.Database.EnsureCreated();
:
Убедитесь, что база данных для контекста существует. Если он существует, никаких действий не предпринимается. Если он не существует, то создается база данных и вся ее схема. Если база данных существует, то не предпринимается никаких действий для обеспечения ее совместимости с моделью для этого контекста.
Я читал, что EntityFrameworkCore
БД в памяти существует только до тех пор, пока существует открытое соединение, и поэтому я попытался явно создать var connection = new SqliteConnection("DataSource=:memory:");
экземпляр и оборачивая приведенный ниже код в using(connection) {}
заблокировать и передать экземпляр подключения options.UseSqlite(connection);
, но DbContext.Database.EnsureCreated();
до сих пор не создает DB-объектов
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();
создать схему, как и ожидалось. Я не представляю приведенный выше код в качестве общего шаблона для запуска интеграционных тестов.
1 ответ
Использование не разделяемой базы данных SQLite в памяти
Базы данных SQLite в памяти по умолчанию являются временными. Как указано в документации:
База данных перестает существовать, как только соединение с базой данных закрывается. Every:memory: база данных отличается от всех остальных.
EF Core's DbContext
с другой стороны, всегда открывает и закрывает соединения с базой данных автоматически, если только вы не пропустили уже открытое соединение.
Следовательно, чтобы использовать одну и ту же базу данных SQLite в памяти для нескольких вызовов в EF Core, необходимо создать 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
,