Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UseInMemoryDatabase with UseInternalServiceProvider. No database provider configured

I'm having trouble injecting a custom IAsyncQueryProvider when using EntityFrameworkCore. To be more precise.. I am having trouble injecting the provider when using the in memory database functionality provided. Using a default provider (SqlServer), all works fine.

Here's my global Startup.cs

private void ConfigureEntityFrameworkWithSecurity(IServiceCollection services)
{
    services
        .AddEntityFramework()
        .AddEntityFrameworkSqlServer()
        .AddScoped<IAsyncQueryProvider, CustomEntityProvider>()
        .AddDbContext<APIContext>((sp, options) =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
                .UseInternalServiceProvider(sp);
        });
}

This works flawlessly, and I can put a breakpoint within CustomEntityProvider to verify that it is indeed being injected. At the moment, CustomEntityProvider simply implements IAsyncQueryProvider, and simply passes through the request. There is no logic contained within it.

When I'm running a test, I configure the webhost to use a different Startup file:

public class TestStartup : Startup
{
    public TestStartup(IHostingEnvironment env) : base(env)
    {
    }

    public override void ConfigureServices(IServiceCollection services)
    {
        services
            .AddDbContext<APIContext>((sp, options) =>
            {
                options.UseInMemoryDatabase()
                    .UseInternalServiceProvider(sp);
            });
        base.ConfigureServices(services);
    }
}

Running a test with TestStartup yields the error:

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 object in its constructor and passes it to the base constructor for DbContext.

And APIContext is correctly defined:

public class APIContext : DbContext
{
    public APIContext(DbContextOptions<APIContext> options)
        : base(options)
    {
    }
    ...
}

Removing UseInternalServiceProvider from TestStartup works correctly - however, I don't want my tests to hit an actual database. Further, I would expect UseInMemoryDatabase to automatically inject dependencies into the service provider - as it works perfectly fine by itself.

The error is confusing because the in memory database is the provider I want to use.

like image 517
Rob Avatar asked Nov 14 '16 10:11

Rob


1 Answers

Unfortunately, the solution is hair-tearingly simple. However, there seems to be very little documentation about using dependency injection with the in-memory database functionality. It appears to be one or the other. Hopefully this question will provide help for future people misfortunate enough to run into this.

I downloaded the EntityFramework source to investigate, and found that calling UseInMemoryDatabase creates an extension InMemoryOptionsExtension which itself will add to the service provider, namely:

public virtual void ApplyServices(IServiceCollection services)
{
    Check.NotNull(services, nameof(services));

    services.AddEntityFrameworkInMemoryDatabase();
}

And the solution is as simple as it looks:

public class TestStartup : Startup
{
    public TestStartup(IHostingEnvironment env) : base(env)
    {
    }

    public override void ConfigureServices(IServiceCollection services)
    {
        services
            .AddEntityFrameworkInMemoryDatabase()
            .AddDbContext<APIContext>((sp, options) =>
            {
                options.UseInMemoryDatabase().UseInternalServiceProvider(sp);
            });
        base.ConfigureServices(services);
    }
}
like image 76
Rob Avatar answered Nov 08 '22 13:11

Rob