Skip to main content

Overview

Entity Framework Core migrations provide a way to incrementally update the database schema to keep it in sync with the application’s data model while preserving existing data. The Tournament Management App uses migrations for two separate contexts:
  • DataContext: Tournament domain entities
  • IdentityDataContext: ASP.NET Core Identity tables

Migration Workflow

The standard workflow for working with migrations involves these steps:
1

Install EF Core Tools

Install the dotnet-ef global tool:
dotnet tool install --global dotnet-ef --version 8
2

Make Model Changes

Modify entity classes in the Torneo.App.Dominio project or update the DataContext/IdentityDataContext configuration.
3

Create Migration

Generate a new migration file that captures the model changes:
# For DataContext
dotnet ef migrations add MigrationName \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

# For IdentityDataContext
dotnet ef migrations add MigrationName \
  --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext
4

Review Migration

Examine the generated migration file in the Migrations folder to ensure it correctly represents your changes.
5

Apply Migration

Update the database schema:
# For DataContext
dotnet ef database update \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

# For IdentityDataContext
dotnet ef database update \
  --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext

Docker Build Migrations

The application’s Dockerfile includes automated migration creation and execution during the build process. This ensures a fresh database schema is always available in the container.

Build Process

Dockerfile (lines 19-35)
# Create and apply DataContext migrations
dotnet ef migrations add InitialCreate \
    --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
    --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
    --context Torneo.App.Persistencia.DataContext \
    --no-build --configuration Release

dotnet ef database update \
    --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
    --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
    --context Torneo.App.Persistencia.DataContext

# Create and apply IdentityDataContext migrations
dotnet ef migrations add CreateIdentitySchema \
    --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
    --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext \
    --no-build --configuration Release

dotnet ef database update \
    --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
    --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
    --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext
The Docker build process creates two migrations:
  • InitialCreate for the DataContext (tournament data)
  • CreateIdentitySchema for the IdentityDataContext (user authentication)

Migration Commands Reference

Creating Migrations

DataContext Migration

Create DataContext Migration
dotnet ef migrations add MigrationName \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

IdentityDataContext Migration

Create IdentityDataContext Migration
dotnet ef migrations add MigrationName \
  --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext

Applying Migrations

Update to Latest Migration

Update DataContext
dotnet ef database update \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext
Update IdentityDataContext
dotnet ef database update \
  --project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Frontend.Areas.Identity.Data.IdentityDataContext

Update to Specific Migration

Target Specific Migration
dotnet ef database update MigrationName \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

Listing Migrations

List DataContext Migrations
dotnet ef migrations list \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

Removing Migrations

Only remove a migration if it hasn’t been applied to any database yet. If the migration has been applied, you must roll it back first.
Remove Last Migration
dotnet ef migrations remove \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

Generating SQL Scripts

Generate SQL scripts without applying them to the database:
Generate SQL Script
dotnet ef migrations script \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext \
  --output migration.sql

Rollback Procedures

To rollback the database to a previous migration:
1

List Available Migrations

View all migrations to identify the target migration:
dotnet ef migrations list \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext
2

Update to Target Migration

Rollback to a specific migration:
dotnet ef database update PreviousMigrationName \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext
3

Remove Unwanted Migrations

After rolling back, remove the migration files:
dotnet ef migrations remove \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext

Rollback to Empty Database

To completely reset the database:
Rollback All Migrations
dotnet ef database update 0 \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext
Using dotnet ef database update 0 will remove all tables from the database. This operation is destructive and will result in data loss.

Common Issues and Solutions

Issue: “No executable found matching command dotnet-ef”

Solution: Install the EF Core tools globally:
dotnet tool install --global dotnet-ef --version 8
Verify installation:
dotnet ef --version

Issue: “Build failed”

Solution: Build the solution before creating migrations:
dotnet build Torneo.App.sln -c Release
Then add the --no-build flag to the migration command:
dotnet ef migrations add MigrationName \
  --project Torneo.App.Persistencia/Torneo.App.Persistencia.csproj \
  --startup-project Torneo.App.Frontend/Torneo.App.Frontend.csproj \
  --context Torneo.App.Persistencia.DataContext \
  --no-build --configuration Release

Issue: “Your startup project doesn’t reference Microsoft.EntityFrameworkCore.Design”

Solution: Ensure the startup project (Torneo.App.Frontend) has the following package reference:
Torneo.App.Frontend.csproj
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  <PrivateAssets>all</PrivateAssets>
</PackageReference>

Issue: “Unable to create an object of type ‘DataContext’”

Solution: Ensure the DATABASE_CONNECTION_STRING environment variable is set:
export DATABASE_CONNECTION_STRING="Data Source=/app/Torneo.db"
Or update the OnConfiguring method in DataContext.cs to use a hardcoded connection string for development.

Issue: Foreign Key Constraint Violations

Solution: The application uses DeleteBehavior.Restrict for all relationships. When deleting entities, you must:
  1. First delete or update all dependent entities
  2. Then delete the parent entity
Example:
Delete Team with Dependencies
// First remove players from the team
foreach (var jugador in equipo.Jugadores)
{
    context.Jugadores.Remove(jugador);
}

// Then remove the team
context.Equipos.Remove(equipo);
context.SaveChanges();

Issue: Migration Conflicts in Docker Build

Solution: If migrations already exist, the Docker build will fail. Either:
  1. Remove existing migrations before building:
    rm -rf Torneo.App.Persistencia/Migrations
    rm -rf Torneo.App.Frontend/Migrations
    
  2. Modify the Dockerfile to check for existing migrations before creating new ones

Migration Files Structure

Migration files are generated in the following locations:
  • DataContext migrations: Torneo.App.Persistencia/Migrations/
  • IdentityDataContext migrations: Torneo.App.Frontend/Migrations/
Each migration consists of three files:
  1. [Timestamp]_[MigrationName].cs - Contains Up() and Down() methods
  2. [Timestamp]_[MigrationName].Designer.cs - Metadata for the migration
  3. [ContextName]ModelSnapshot.cs - Current state of the entire model

Example Migration Structure

Migration Up Method
public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Municipios",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("Sqlite:Autoincrement", true),
                Nombre = table.Column<string>(maxLength: 50, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Municipios", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "Municipios");
    }
}

Environment Variables

The following environment variables affect migration behavior:
DATABASE_CONNECTION_STRING
string
default:"Data Source=/app/Torneo.db"
SQLite connection string. Override to use a different database location or SQL Server.
MSSQL_SA_PASSWORD
string
SQL Server SA password. Only required if using SQL Server instead of SQLite.

Best Practices

  1. Always review migrations before applying them to production databases
  2. Test migrations on a development database first
  3. Back up your database before applying migrations in production
  4. Use descriptive migration names that indicate what changed (e.g., AddPhoneNumberToDirector, CreatePartidoTable)
  5. Never modify applied migrations - create a new migration instead
  6. Keep migrations small - one logical change per migration
  7. Generate SQL scripts for production deployments instead of running migrations directly

Data Contexts

Learn about DataContext and IdentityDataContext

Domain Entities

Explore the domain model entities

Build docs developers (and LLMs) love