Я пишу ASP.Net Core Web API, используя EF Core с SQL-сервером и StructureMap для IOC.
Все проекты библиотеки .NetStandard2.0
, а API и модульные тесты - NetCoreApp2.0
.
Для модульных тестов я использую XUnit и заменяю SQL Server на базу данных SQLite в памяти, поскольку она обеспечивает полную ссылочную целостность.
Я все еще использую ту же настройку IOC, что и мой основной код, но я передаю список реестров структурной карты, чтобы я мог заменить их в фабрике контекста SQLite, а не в SQL.
Это урезанная версия моего DbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions options) : base(options)
{
}
}
и это мой код настройки теста:
public IServiceProvider ServiceProvider { get; set; }
public MyServiceTests()
{
var services = new ServiceCollection();
var dataRegistry = new Registry();
dataRegistry.For<IContextFactory<MyDbContext>>().Use<SqlLiteContextFactory>();
if (SqlLiteContextFactory.Connection == null)
{
SqlLiteContextFactory.Connection = new SqliteConnection("DataSource=:memory:");
SqlLiteContextFactory.Connection.Open();
}
services
.AddEntityFrameworkSqlite()
//This is only here as some posts suggested this was needed, StartUp.cs for production site does not have this and works fine.
.AddDbContext<MyDbContext>(options => options.UseSqlite(SqlLiteContextFactory.Connection));
var registries = new List<Registry>
{
dataRegistry,
new CommandQueryRegistry(),
new ServiceRegistry(),
new TransformRegistry()
};
ServiceProvider = DependencyInjection.TestSetup(services, registries);
}
Код инициализации IOC довольно прост:
public static IServiceProvider TestSetup(IServiceCollection services, List<Registry> registries)
{
var container = new Container();
var registry = new Registry();
registries.ForEach(r => registry.IncludeRegistry(r));
container.Configure(config =>
{
config.AddRegistry(registry);
config.ForSingletonOf<IHttpContextAccessor>().Use<HttpContextAccessor>();
config.Populate(services);
});
var serviceProvider = container.GetInstance<IServiceProvider>();
return serviceProvider;
}
Это код фабрики контекста для моей фабрики контекста SQLite, единственное различие между этой и SQL-фабрикой заключается в том, что у меня есть статическое свойство Connection
, чтобы гарантировать, что я не потеряю БД после удаления контекста.
public class SqlLiteContextFactory : IContextFactory<MyDbContext>
{
public static SqliteConnection Connection;
private DbContextOptions<MyDbContext> CreateOptions(bool trackChanges)
{
var builder = new DbContextOptionsBuilder<MyDbContext>();
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite(Connection);
if (!trackChanges)
{
builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
return builder.Options;
}
private MyDbContext CreateDbContext(bool trackChanges)
{
if (Connection == null)
{
Connection = new SqliteConnection("DataSource=:memory:");
Connection.Open();
}
var context = new MyDbContext(CreateOptions(trackChanges));
//Always keep context as most recent version in tests
context.Database.Migrate();
return context;
}
public MyDbContext CreateNonTrackedContext()
{
return CreateDbContext(false);
}
public MyDbContext CreateDbContext()
{
return CreateDbContext(true);
}
}
Проблема
Мой код работает нормально при запуске сайта, фабрика контекста SQL создает контекст и запускает команду migrate, чтобы без проблем создать базу данных.
Однако, когда я пытаюсь протестировать какие-либо из моих сервисов с помощью модульных тестов, фабрика контекста взрывается при попытке запустить Migrate
со следующим:
System.InvalidOperationException : No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetRelationalService[TService](IInfrastructure`1 databaseFacade)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
at API.Test.Utilities.SqlLiteContextFactory.CreateDbContext(Boolean trackChanges) in API.Test\Utilities\SqLiteContextFactory.cs:line 37
at API.Test.Utilities.SqlLiteContextFactory.CreateDbContext() in API.Test\Utilities\SqLiteContextFactory.cs:line 49
at API.CommandQueries.Commands.MyCommand.AddOrUpdate(MyModel model) in \API.CommandQueries\Commands\MyCommand.cs:line 21
at API.Services.MyService.Save(Model model) in API.Services\MyService.cs:line 40
at API.Test.MyTests.CanAdd() in API.Test\MyServiceTests.cs:line 47
Я перепробовал каждое решение этой проблемы, которое смог найти. Добавление в .AddDbContext
службы коллекции. Убедитесь, что тестовый проект и контекстный проект имеют ссылки на EntityFrameworkCore
, EntityFrameworkCore.Relational
и EntityFrameworkCore.Sqlite
. Убедитесь, что соединение SQLite поддерживается, и убедитесь, что для настройки теста используется .AddEntityFrameworkSqlite()
на ServiceCollection
.
Я также пытался заменить SQLite для ядер EF InMemory
Db с еще одной фабрикой контекста, но это не помогло с точно такой же проблемой.
Кто-нибудь еще сталкивался с этой проблемой раньше, или я использую EF Core каким-то образом, который делает его несовместимым с SQLite?