9

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.

Rob
  • 26,989
  • 16
  • 82
  • 98

2 Answers2

14

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);
    }
}
Rob
  • 26,989
  • 16
  • 82
  • 98
  • This is the exact error I am encountering. However I'm using EntityFramework.InMemory v2.0.0 and `UseInMemoryDatabase()` has become obsolete. You're right the documentation is limited and I'm guessing `services.AddEntityFrameworkInMemoryDatabase().AddDbContext<` is the new way to do it. Although every which way I have this I still get the error. Any chance you've upgraded the Nuget package in the last year? Thanks. – Jeremy Thompson Oct 23 '17 at 03:49
  • @JeremyThompson I didn't end up taking my project very far; it was more of an experiment with .netcore than anything else. I'm not sure if `.AddEntityFrameworkInMemoryDatabase` is the *new* way to do it. As far as I remember, `options.UseInMemoryDatabase()` configures the context to use the in-memory provider, while `.AddEntityFrameworkInMemoryDatabase()` configures the dependency injection - so both need to be specified. I'll take a look when I'm home, after upgrading to latest. – Rob Oct 23 '17 at 04:02
  • [This question](https://stackoverflow.com/questions/43098065) or [this one](https://stackoverflow.com/questions/43991088) may be related to the problem you're having – Rob Oct 23 '17 at 04:02
  • @JeremyThompson Unfortunately, I've been unable to get the solution into working condition to try it out on version 2.0.0. Did the above links help at all? – Rob Oct 23 '17 at 09:20
  • 1
    Thanks for your help Rob, this answer shows the correct way to do it: https://stackoverflow.com/a/38897560/495455 - owe you a beer @ The Nixon Bar most lunch times. – Jeremy Thompson Oct 24 '17 at 00:06
  • @JeremyThompson Hmm, I'm curious how that works but the above doesn't - they both appear to be adding to the configuration builder. Anyway, glad you got it sorted in the end! – Rob Oct 24 '17 at 00:55
-6

Click on -> properties Folder inside your solution Explorer. Open -> launchSettings.json file api/TodoItems

"profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } },

"profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api/TodoItems", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } },