0

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.

2
  • 2
    Depends on how you register the context (which you have not shown) Commented Aug 30, 2019 at 20:29
  • 2
    Without a minimal reproducible example that highlight exactly what was done, it’s hard to reproduce the problem, allowing a better understanding of what is being asked. Commented Aug 30, 2019 at 20:30

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

0

Where are storing the connections string value? I would check the source. Out of the box asp.net core has the a application.settings.json file configured for injection. AZ Function does not do this. If you are using an application.settings.json then you have to configure it to load settings from that file.

Here a sample how to load a config file in DI that allows you to have similar access to the content as in asp.net core:

var config = new ConfigurationBuilder().SetBasePath(Environment.CurrentDirectory)
                .AddJsonFile("application.settings.json", optional: false, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();
builder.Services.AddSingleton<IConfiguration>(config);

And getting a value in the Configure method: string SqlConnectionString = config.GetConnectionString("SqlConnectionString");

This is done in the public override void Configure(IFunctionsHostBuilder builder). Here is how to use DI in Azure Functions.

The other possibility I can think of is Azure Key Vault or environment variables.

3 Comments

Thank you but the problem isn't related to getting the connection string. It is reading it from the local.settting.json file just fine. The problem is .Net is using the wrong constructor.
In the DbContext how does your constructor look? The DbContext contructor can override the DI process.
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { //do whatever you need here, like Database.EnsureCreated(); }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.