How does entity framework migration deal with DbContext?

In EntityFrameworkCore, if you want to create migration files for your DbContext, you would like to run a command like this

> dotnet ef migrations add InitMigration

Do you know how the ‘migrations add’ works?

There are various ways to create DbContext in the migration, do you know that?

In this article, I will give you the answer to the questions above.

I won’t dig into the migration script format or how are they translated into SQL language to execute in the database system. It will tell you about how we initialize our DbContext.

Overview

In this post, I will use three packages

Microsoft.EntityFrameworkCore: is the core package of EntityFramework, It responses for the functional feature, It doesn’t decide how the application connect to your database infrastructures.

Microsoft.EntityFrameworkCore.SqlServer: database provider for Entity Framework Core. Which helps your application communicate with the database system. If you want to use SqlLite, then you might want to use another package for SqlLite.

Microsoft.EntityFrameworkCore.Design: a package that contains shared design-time components for Entity Framework Core tools (dotnet ef migration…). You only need this package if you use migrations.

The add migration command requires a derived DbContext instance to be created at design time in order to gather details about the application’s entity types and how they map to a database schema.

The migration will try to get required context options from DbContext instance. (e.g It will try to get your database provider.)

There are various ways to create the DbContext:

  1. From application services: that look for DbContext in your application Service Provider. It requires us to pass DbContextOptions<TContext> to DbContext constructor. You will set context options then you register your DbContext to Service Provider.
  2. By overriding OnConfiguring method: If the DbContext can’t be obtained from Service Provider. Then It will create DbContext using the constructor with no parameters. You will set context options inside OnConfiguring  method.
  3. From design-time factory: You will tell the tool how to create DbContext by implementing IDesignTimeDbContextFactory<>. If you go with this way, It will ignore other ways of creating CbContext.


A. Create DbContext from application services

This is the most popular way to use Entity Framework for a Web Project.

There are 5 steps that are used by the tools to add migrations.

1. Looking for executing project

Normally, you should execute the dotnet ef command inside the project directory.

If It can’t find any project. It will throw an error:

No project was found. Change the current working directory or use the –project option.

> dotnet ef migrations add InitMigration
No project was found. Change the current working directory or use the --project option.

Then you have to:

  • Change the current working directory.
  • (or) use the –project option to tell the project name.

2. Initialize Service Provider

If you use the project template from Microsoft.

It will

  1. Execute “public static IWebHostBuilder CreateWebHostBuilder” in Program.cs
  2. Create an instance of Startup object from the service provider
  3. Execute Startup.ConfigureServices(). You have to register your DbContext inside this ConfigureServices() method.

3. Read registered DbContext and database provider from Service Provider

To register a DbContext to Service Provider, in Startup.cs you should add your registration inside ConfigureServices():

namespace FromServiceProvider.Web
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyDbContext>(options =>
            options.UseSqlServer(_config.GetConnectionString("DefaultConnection")));
        }

    }
}

The options.UseSqlServer() is an extension method from Microsoft.EntityFrameworkCore.SqlServer package. It tells that you are using provider ‘Microsoft.EntityFrameworkCore.SqlServer’.

If you neither call services.AddDbContext() or options.UseSqlServer(). When you add the migration, you will receive an error like this

> dotnet ef migrations add InitMigration
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<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)

TIP: If there are multiple registered DbContext. The tool will throw an error and ask you to use –context option to select required context.

4. Creating DbContext and looking for database provider config

If AddDbContext is used, you also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.

If you didn’t pass DbContextOptions<> to your constructor. You properly get this error:

> dotnet ef migrations add InitMigration
An error occurred while accessing the IWebHost on class 'Program'. Continuing without the application service provider. Error: AddDbContext was called with configuration, but the context type 'MyDbContext' only declares a parameterless constructor. This means that the configuration passed to AddDbContext will never be used. If configuration is passed to AddDbContext, then 'MyDbContext' should declare a constructor that accepts a DbContextOptions<MyDbContext> and must pass it to the base constructor for DbContext.
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<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()

...
...
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<TContext> object in its constructor and passes it to the base constructor for DbContext.

To resolve this error, you should add DbContextOptions<> to your constructor

namespace FromServiceProvider.Web.Models
{
    public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {

        }
        public DbSet<User> Users { get; set; }
    }
}

5. It will create Migration scripts in the Migrations folder in the project that holds your execution context.

Because I’m executing the migration for project ‘FromServiceProvider.Web’. Then It will add migrations in FromServiceProvider.Web /Migrations

(Migrations will live in executing project)


B. Create DbContext by overriding OnConfiguring

This way is less popular. It might suitable with an application what do not use Dependency Inject / Service Provider.

Or you need to simply initialize like this:

using (var context = new MyDbContext())
{
  // do stuff
}

There are also 5 steps for the tools to create migration scripts.

1. Looking for executing project

The same as in the section (A). It will try to indicate the executing project.

2. Looking and creating DbContext instance

In this approach, the migration will look for the derived DbContext type inside the project.

Then will create an instance of DbContext using a constructor with no parameters.

3. Executing DbContext.OnConfiguring()

You should add your database provider config and other context options in OnConfiguring() method.

The code looks like this:

namespace FromOnConfiguring.ConsoleApp.Models
{
    public class MyDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=MyDb;Integrated Security=True;");
        }
    }
}

4. Looking for database provider config from created DbContext()

The migration needs database provider information. It will try to get provider information from your DbContext.

5. It will create Migration scripts in the Migrations folder in the project that holds your execution context

Finally, It will create the migration scripts.


C. Create DbContext from IDesignTimeDbContextFactory

This is another approach to create DbContext.

Say you run into a situation:

  • You do not use Dependency Injection.
  • You want to add custom parameters to DbContext constructors.
    In the normal flow, if there a constructor, It expects to receive single constructor DbContextOptionsBuilder<>. In some situation, you don’t want to use it, or you want to use more parameter.
  • You do not use Program.BuildWebHost in your application.
    e.g You are running a console application. In this situation you won’t use approach (A) – create DbContext from Application Services.

Below are 3 steps that are used by the tool to create DbContext and migration scripts.

1. The tools will look for IDesignTimeDbContextFactory<> implementation in executing project.

Your factory looks like this:

namespace FromDesignTimeFactory.ConsoleApp
{
    public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
            optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=MyDb;Integrated Security=True;");

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

2. It will execute CreateDbContext() in your factory to create DbContext

You should put your database provider configuration in this CreateDbContext() method.

3. It will create Migration scripts in the Migrations folder in the project that holds your execution context

Finally, It will create the migration scripts.


D. Conclusion

There are 3 main steps to create a migration: Create DbContext, read database provider information, add migration script.

There are also 3 ways to create a DbContext during the migration. Depend on your requirements. You should choose the most suitable approach.

  • From Application Service: that is suitable with Web Application that uses dependency inject.
  • By overriding  OnConfiguring method: when there is no dependency inject or you simply want to create DbContext without constructors.
  • From IDesignTimeDbContextFactory: when you don’t implement Program.BuildWebHost or you need a custom constructor for DbContext. 

You can find the source code of this post in this repository: https://github.com/hung-doan/hungdoan.com-ef-migration-add

E. References

[1] https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation

[2] https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontex

[3] https://github.com/aspnet/Announcements/issues/258

3 thoughts on “How does entity framework migration deal with DbContext?

  1. Viktor Fursov says:

    Thanks, 2nd option did help me
    By overriding OnConfiguring method: when there is no dependency inject or you simply want to create DbContext without constructors.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.