Azure Function Uses Wrong DbContext Constructor

asp.net-core azure-functions entity-framework-core

Question

I have an existing EF Core 2.2 DbContext that works fine in an ASPNET Core application as well as LinqPad. Now I am trying to add it to an Azure function. In both ASPNET and the Azure function I am using dependency injection.

The DbContext class has three constructors - an empty one, one that takes a connection string and another that takes a DbOptionsBuilder instance. The ASPNET Core app seems to invoke the one that takes the DbOptionsBuilder instance while LinqPad uses the one that takes the connection string. As I said, both of these work fine.

The Azure function app tries to use the one that takes a string, but it passes null instead of a value. This causes an error later saying that a provider hasn't been configured.

I can force the function app to use the DbOptionsBuilder constructor by removing the one that takes a string. When I do this the function app works fine. However, I can no longer use the context in LinqPad if I do.

My question is, first, how can I make the Azure function call the appropriate constructor without removing the others? Second, and less importantly, why the different behavior between the ASPNET runtime and the Azure function runtime?

EDIT I am only running the AZ function locally at this point so it is reading the connection string from 'local.settings.json' file. This part is working.

Here is the Startup.Configure method of the function project.

public class Startup : FunctionsStartup
{
    /// <summary>
    /// This method gets called by the runtime. Use this method to add services to the DI container.
    /// </summary>
    /// <param name="builder">The function host builder</param>
    public override void Configure(IFunctionsHostBuilder builder)
    {
        // Add database context

        string env = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT");
        string connectionString = Environment.GetEnvironmentVariable($"ConnectionStrings:{env}");

        builder.Services.AddDbContext<FullContext>(x => x.UseSqlServer(connectionString), ServiceLifetime.Transient);
    }
}

As I said, it is reading the connection string and appears to pass it to the AddDbContext method. But something is going wrong somewhere.

EDIT 2 Here are the three constructors from my DbContext subclass. Nothing special. Also including the OnConfiguring method.

    public FullContext() { }

    public FullContext(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public FullContext(DbContextOptions<FullContext> options) : base(options) { }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (ConnectionString != null)
            optionsBuilder.UseSqlServer(ConnectionString);

        base.OnConfiguring(optionsBuilder);
    }

EDIT 3 After reviewing the link @Jack Jia suggested I tried the following.

First, I create my own instance of the DbContextOptionsBuilder and specify the provider and connection string.

    var options = new DbContextOptionsBuilder<FullContext>();
    options.UseSqlServer(connectionString);

I then try to force the DI service to use these options. However, this fails when using the AddDbContext method - it still tries to call the wrong constructor using a null string as the parameter.

In other words, this fails:

builder.Services.AddDbContext<FullContext>(x => new FullContext(options.Options), ServiceLifetime.Transient);

but this seems to work:

builder.Services.AddTransient<FullContext>(x => new FullContext(options.Options));

Assuming I am understanding the docs correctly both calls should be forcing the DI service to use the constructor taking an DbContextOptions parameter. But this doesn't seem to be the case.

1
0
9/3/2019 5:51:45 PM

Popular Answer

You may refer to: Service registration methods

If there are multiple constructors, you can specify one as following:

Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

For example:

// Constructor1
builder.Services.AddScoped<IMyDep>(sp => new MyDep());

// Constructor2
builder.Services.AddScoped<IMyDep>(sp => new MyDep("A string!"));

// Constructor3
builder.Services.AddScoped<IClass1, Class1>();
builder.Services.AddScoped<IMyDep>(sp =>
{
    IClass1 class1 = sp.GetRequiredService<IClass1>();
    //class1.doSomething(...);
    return new MyDep(class1);
});

So, you do not need to change the DbContext class, just specifically use different constructors in different apps.

1
9/2/2019 3:17:42 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow