Database Initialisation Strategies for Entity Framework Core

In this post, I will demonstrate strategies for database initialisation during early and late development stages. The focus is on productively and effectively leveraging EF Core and its built-in capabilities for keeping your data model and database schema in sync.

Automatic Initialisation

First, the application should be configured to automatically initialise the database on startup.

Create DbInitialiser.cs with the following code:

public class DbInitialiser
{
    private readonly ApplicationDbContext _context;

    public DbInitialiser(ApplicationDbContext context)
    {
        _context = context;
    }

    public void Run()
    {
        // TODO: Add initialisation logic.
    }
}

Add the new initialiser to dependency injection. Update Program.cs with the following code:

builder.Services.AddTransient<DbInitialiser>();

Automatically run the initiliser on application startup. Update Program.cs with the following code:

using var scope = app.Services.CreateScope();

var services = scope.ServiceProvider;

var initialiser = services.GetRequiredService<DbInitialiser>();

initialiser.Run();

Early Development

During early development, the data model will change frequently. Entities and properties will be created, updated, and removed at a staggering pace. A lightweight strategy is to recreate the database on application startup, and optionally, seed the database with sample data.

In Data/DbInitialiser.cs, update the Run method with the following code:

public void Run()
{
    _context.Database.EnsureDeleted();
    _context.Database.EnsureCreated();

    // TODO: Seed with sample data
}

The preceding code results in the following database initialisation:

  • EnsureDeleted will drop the database if it exists, and
  • EnsureCreated will create the database and initialise the database schema, and
  • The database will be seeded with sample data.

Seeding the database with sample data is important as existing data will not be preserved when the database is deleted. This strategy requires minimal effort to maintain data model changes and includes the benefit of ensuring the database will be in a clean state after application startup.

Late Development

Once initial development has been completed the data model will change less frequently. During this stage, a good strategy for database initialisation is to use EF Core migrations to update the database schema on application startup, and optionally, seed the database with sample data.

In Data/DbInitialiser.cs, update the Run method with the following code:

public void Run()
{
    _context.Database.Migrate();

    // TODO: Seed with sample data
}

The preceding code results in the following database initialisation:

  • Migrate will create the database if it doesn’t already exist and apply any pending migrations, and
  • The database will be seeded with sample data.

Note that EnsureDeleted and EnsureCreated have been removed. Databases created with EnsureCreated are not supported by EF Core migrations.

Since this strategy upgrades, rather than recreating the database, all existing data will be preserved. This strategy will require some effort to maintain data model changes as you will be adding, updating, and (in some cases) removing migrations. You may also need to merge your data model changes with those from another developer.

Deployment

The previous strategy initialises the database on application startup while preserving existing data. This is a productive and simple strategy for local development environments. However, it is not appropriate for other environments such as staging and production. For these environments database initialisation should occur during deployment.

Using the EF Core CLI tools there are numerous approaches, here are two popular choices:

  1. Generate idempotent SQL Scripts on build, and then execute those scripts during deployment:
    dotnet ef migrations script --idempotent --output migrations.sql
  2. (Recommended) Generate a migration bundle, and then execute the bundle on deployment:
    dotnet ef migrations bundle

The simplest approach is migration bundles (requires EF Core 6). However, both will create the database if it doesn’t exist and then apply any pending migrations.

Recommended Resources

For additional information, review the following resources:

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Matt
2 years ago

Hi Jason, I love this! Thanks for sharing.

I was wondering whether you think even for early development, there is value in wrapping the EnsureCreated/Deleted calls in a check for a flag? You could put something in appsettings.Development.json, or even secrets. Or make it dependent on a debug or development environment check.