在内存中使用 SQLite 运行 EF Core - 未配置数据库提供程序

Posted

技术标签:

【中文标题】在内存中使用 SQLite 运行 EF Core - 未配置数据库提供程序【英文标题】:Running EF Core with SQLite in memory - No Database Provider has been configured 【发布时间】:2018-12-14 05:00:36 【问题描述】:

我正在使用带有 SQL 服务器的 EF Core 和用于 IOC 的 StructureMap 编写一个 ASP.Net Core Web API。 库项目都是.NetStandard2.0,而API 和单元测试是NetCoreApp2.0

对于单元测试,我正在运行 XUnit 并将 SQL Server 换成内存中的 SQLite db,因为它提供了完整的引用完整性。 我仍在使用与我的主要代码相同的 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 时崩溃:

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 添加到服务集合中。确保测试项目和上下文项目都引用了EntityFrameworkCoreEntityFrameworkCore.RelationalEntityFrameworkCore.Sqlite。确保 SQLite 连接保持活动状态,并确保测试设置在 ServiceCollection 上使用 .AddEntityFrameworkSqlite()

我还尝试使用另一个上下文工厂将 SQLite 换成 EF Cores InMemory Db,但失败并出现完全相同的问题。

之前有没有其他人看到过这个问题,或者我是否以某种方式使用 EF Core,导致它在某种程度上与 SQLite 不兼容?

【问题讨论】:

【参考方案1】:

我前一阵子研究过这个。而且我相信您需要运行迁移并保持连接打开。您正在覆盖连接静态。另外,我不确定它是在您使用连接之前创建的。

【讨论】:

以上是关于在内存中使用 SQLite 运行 EF Core - 未配置数据库提供程序的主要内容,如果未能解决你的问题,请参考以下文章

SQLite EF Core - '外键约束失败'

在单元测试期间避免在EF Core 2.2中使用HasData Seed DbContext

.Net Core EF Core之Sqlite使用及部署

基于Asp.net core + EF + Sqlite 5分钟快速上手一个小项目

EF Core / Sqlite 一对多关系在唯一索引约束上失败

SQLite EF Core Database Provider