EF migrations connection source by priority

May 24th 2024 EF Core .NET

There are many different ways for EF Core tools to get the connection string for the database to update using migrations. They are all listed in the documentation, but the order of priority in which they are used is unfortunately not very clear.

  1. If your project implements the IDesignTimeDbContextFactory<TContext> interface for your DbContext, then the DbContext returned by its CreateDbContext method is always going to be used for running the migrations. The connection string is usually going to be hardcoded in the method when using this approach:

    public class DesignTimeSampleDbContextFactory : IDesignTimeDbContextFactory<SampleDbContext>
    {
        public SampleDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<SampleDbContext>();
            optionsBuilder.UseSqlServer(
                @"Data Source=(localdb)\mssqllocaldb;Initial Catalog=SampleFromFactory;Integrated Security=True"
            );
    
            return new SampleDbContext(optionsBuilder.Options);
        }
    }
    
  2. If there's no design-time factory class in your project, then the tools will attempt to get a DbContext instance from the application IHostBuilder. It's the same approach as used by your application at run time. You register the DbContext to be used with an AddDbContext call. The connection string will usually be retrieved from a .NET configuration provider (e.g. read from a configuration file or environment variable):

    builder.Services.AddDbContext<SampleDbContext>(options =>
    {
        options.UseSqlServer(builder.Configuration.GetConnectionString("Sample"));
    });
    
  3. If you're not using the IHostBuilder or the DbContext is not registered with it, then the tools will try to instantiate a DbContext by calling its constructor with no parameters. In this case, you'll have to override its OnConfiguring method to set the connection string. If your DbContext also has a constructor with a DbContextOptions parameter, you should add a check to the OnConfiguring method to not override the connection string from there:

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer(
                @"Data Source=(localdb)\mssqllocaldb;Initial Catalog=SampleFromOnConfiguring;Integrated Security=True"
            );
        }
    }
    

In all three cases, you can override the connection string from the command line if you add the --connection argument:

dotnet ef database update --connection 'Data Source=(localdb)\mssqllocaldb;Initial Catalog=SampleFromCommandLine;Integrated Security=True'

Of course, this will only work if the code creating the DbContext instance to use does not throw an exception.

All the behavior described above remains the same even if you use the EF Core tools to build a bundle executable and run that instead of the dotnet ef database update command. You can override the connection string with the --connection argument when running the generated bundle executable:

dotnet ef migrations bundle
.\efbundle.exe --connection 'Data Source=(localdb)\mssqllocaldb;Initial Catalog=SampleFromCommandLine;Integrated Security=True'

If you want to try this out for yourself, check the sample project in my GitHub repository. I created it to verify if I interpreted the documentation correctly. The final commit uses the design-time factory, and the ones before that the other two approaches. I used a different database name in each connection string to easily check which database was created in my SQL Server LocalDB instance. I dropped the created database after each run to start with a clean state.

Database created with migrations

The EF Core tooling gives you a lot flexibility when setting up your process for running the migrations. And it's usually a part of the project you don't need to touch after the initial setup. Which gives you a lot of time to forget the details by the time you need to set up a new project. And then you end up wondering why it's not working as expected. Documentation can help, but in the end I had to create a dedicated sample project to fully understand the order of priority for all supported ways of creating a DbContext at design time.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License