Implement IDesignTimeDbContextFactory with ConfigurationBuilder

TL;DR

public class DerivedDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory<DerivedDbContext>
{
    private const string _databaseName = "DerivedDb";
    private const string _migrationAssembly = "DerivedDb.Migrations";

    public DerivedDbContext CreateDbContext(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder();
        var configuration = configurationBuilder
            .AddUserSecrets<Startup>()
            .AddEnvironmentVariables()
            .Build();

        var connectionString = configuration.GetConnectionString(_databaseName);

        var dbContextOptionsBuilder = new DbContextOptionsBuilder<DerivedDbContext>()
            .UseMySql(connectionString, x => x.MigrationsAssembly(_migrationAssembly));

        return new DerivedDbContext(dbContextOptionsBuilder.Options);
    }
}

It all starts with an error message.

Unable to create an object of type 'DerivedDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

I stumbled upon this issue when trying to create a new migration using the dotnet CLI. In particular, I was trying to run the command: dotnet ef migrations add.

With the verbose flag added, I saw the full trail of the logs. It boiled down to the inability of the framework to find a parameterless constructor to create the DbContext.

System.MissingMethodException: No parameterless constructor defined for this object.

Here’s the full log:

Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider...
Finding IWebHost accessor...
Using environment 'Development'.
Using application service provider from IWebHost accessor on 'Program'.
Finding DbContext classes in the project...
Found DbContext 'DerivedDbContext'.
Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create an object of type 'DerivedDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 ---> System.MissingMethodException: No parameterless constructor defined for this object.

I find the error message quite interesting especially for being a relative n00b to the whole dotnet CLI thing. Previously, I was solely dependent on the PowerShell commands and Package Manager Console in Visual Studio.

The link provided goes to an article about Design-time DbContext Creation. It explains why for certain EF commands like migrations, an instance of the Derived DbContext must be created at design-time so that information about the types and schema mapping can be gathered.

In there, it talks about a generic interface that can be implemented to address this issue.

IDesignTimeDbContextFactory<TContext>

According to the EF Core docs, it is

A factory for creating derived DbContext instances. Implement this interface to enable design-time services for context types that do not have a public default constructor. At design-time, derived DbContext instances can be created in order to enable specific design-time experiences such as Migrations.

Moreover, implementations of this interface can be automatically detected by the framework in the startup assembly or in the assembly where you store your migrations. Neat! No need to explicitly register it somewhere.

Note that in the error message above, the second line of the log confirms this behavior: Finding IDesignTimeDbContextFactory implementations...

Implementing the interface is quite easy. The linked article actually provides an example implementation.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace MyProject
{
    public class BloggingContextFactory : IDesignTimeDbContextFactory<BloggingContext>
    {
        public BloggingContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
            optionsBuilder.UseSqlite("Data Source=blog.db");

            return new BloggingContext(optionsBuilder.Options);
        }
    }
}

We only need to implement a single method, CreateDbContext(), that accepts as its parameter, an array of strings. Oddly enough, the string array is currently not being used to configure anything.

The example is also a bit limited. It hard-codes the database connection string. It is a good start but it can be made better by having it to load configuration options from an array of providers instead.

Let’s create a new class in our startup assembly that implements the IDesignTimeDbContextFactory<T>. The key to this approach is the ConfigurationBuilder. This class provides access to APIs for loading from configuration sources. For situations like this, I always get my settings from the user secrets file and/or the environment variables.

REMEMBER: Don't put sensitive information, like login credentials or database connection settings, into a file that will be checked in to a repository! I'm looking at you appsettings.json. -.-

This is how it looks like.

var configurationBuilder = new ConfigurationBuilder();
var configuration = configurationBuilder
    .AddUserSecrets<Startup>()
    .AddEnvironmentVariables()
    .Build();

The next step in the exercise is the actual loading of the configuration value (in our case, the database connection string) from the configuration object that we just built. We utilize the convenience function, GetConnectionString(), to do just that. Make sure that the format of your configuration is like this:

{
    "ConnectionStrings": {
        "DerivedDb": "connection string"
    }
}

or

"ConnectionStrings:DerivedDb": "connection string"

Next, we create a DbContextOptionsBuilder<T>() instance, providing it with our newly-extracted connection string as well as specifying the location of our migrations. In this case, it is in a separate assembly apart from the startup.

Finally, with the builder configured, we return an instance of our derived DbContext, passing to it, the Options property of our DbContextOptionsBuilder().

The final class should look like this.

public class DerivedDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory<DerivedDbContext>
{
    private const string _databaseName = "DerivedDb";
    private const string _migrationAssembly = "DerivedDb.Migrations";

    public DerivedDbContext CreateDbContext(string[] args)
    {
        var configurationBuilder = new ConfigurationBuilder();
        var configuration = configurationBuilder
            .AddUserSecrets<Startup>()
            .AddEnvironmentVariables()
            .Build();

        var connectionString = configuration.GetConnectionString(_databaseName);

        var dbContextOptionsBuilder = new DbContextOptionsBuilder<DerivedDbContext>()
            .UseMySql(connectionString, x => x.MigrationsAssembly(_migrationAssembly));

        return new DerivedDbContext(dbContextOptionsBuilder.Options);
    }
}

Moment of truth

Let us now re-execute our migrations command.

dotnet ef migrations add MigrationName --project ../DerivedDb.Assembly

Note that you MAY have to specify either the path to your migrations assembly using the --project flag or the path to your startup assembly using the --startup-project flag. It depends on which assembly you happen to be when you run dotnet ef migrations.

For the heck of it, I added the verbose flag to see the full log. We can see that the framework is now able to see an implementation of the IDesignTimeDbContextFactory and can now use it.

Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'DerivedDbDesignTimeDbContextFactory'.
Found DbContext 'DerivedDbContext'.
Finding application service provider...
Finding IWebHost accessor...
Using environment 'Development'.
Using application service provider from IWebHost accessor on 'Program'.
Finding DbContext classes in the project...
Using DbContext factory 'DerivedDbDesignTimeDbContextFactory'.
Using context 'DerivedDbContext'.
Finding design-time services for provider 'Pomelo.EntityFrameworkCore.MySql'...
Using design-time services from provider 'Pomelo.EntityFrameworkCore.MySql'.
Finding design-time services referenced by assembly 'DerivedDb.API'.
No referenced design-time services were found.
Finding IDesignTimeServices implementations in assembly 'DerivedDb.API'...
No design-time services were found.
DetectChanges starting for 'DerivedDbContext'.
DetectChanges completed for 'DerivedDbContext'.
Writing migration to 'DerivedDb.Migrations/Migrations/20190901093106_MigrationName.cs'.
Writing model snapshot to 'DerivedDb.Migrations/Migrations/DerivedDbContextModelSnapshot.cs'.
'DerivedDbContext' disposed.
Done. To undo this action, use 'ef migrations remove'

That’s it!

Hope you find this informative. Happy coding.

%d bloggers like this: