entity-framework-code-first

Code First in Entity Framework

  • 6 min

Code-First is a development approach in Entity Framework where data models are defined using C# classes.

In this model, we start from C# classes to define our data structure. These classes (which we call entities) represent the tables in the database, and their properties represent the columns.

Subsequently, Entity Framework automatically takes care of generating the corresponding database (including tables, relationships, and constraints) from these entities.

This approach is especially useful in new projects, where there is no predefined database, or in projects where full control over the database design through code is preferred.

This contrasts with the Database First approach, where we start from an existing database and the classes are generated automatically.

Workflow with Code-First

The workflow with Code-First consists of the following steps:

  1. Define the model: We create classes that represent our data (entities and DbContext).
  2. Configure relationships: We configure relationships and constraints through code.
  3. Migrations: Generate and apply migrations to create or update the database.
  4. Operations: Perform operations as we would normally.

The data model

We have seen how to create the model, relationships, and operations in the rest of the course. So I won’t dwell too much here.

But, as a summary, let’s assume we have a model like this

Entities are C# classes that represent the tables in the database. Each property of the class represents a column in the table.

For example, if we want to create a Products table, we define the following class:

public class Product
{
    public int Id { get; set; } // Primary key
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Stock { get; set; }
}

In this example:

  • Id is the primary key of the table. Entity Framework automatically recognizes properties named Id or ClassNameId as primary keys.
  • Name, Price, and Stock are columns in the table.

The context is a class that inherits from DbContext and acts as a bridge between the entities and the database. This class contains properties of type DbSet<T>, which represent the tables in the database.

public class MyContext : DbContext
{
    public DbSet<Product> Products { get; set; } // Represents the Products table

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Configure the database connection
        optionsBuilder.UseSqlServer("Server=myServer;Database=MyDatabase;Trusted_Connection=True;");
    }
}

In this example:

  • Products is a DbSet that represents the Products table.
  • OnConfiguring is used to configure the connection string to the database.

Entity Framework allows you to define relationships between entities, such as one-to-one (1:1), one-to-many (1), and many-to-many (N) relationships. These relationships can be configured using Data Annotations or Fluent API.

Let’s assume we want to add a relationship between Product and Category. A product belongs to a category, and a category can have many products.

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Product> Products { get; set; } // 1:N relationship
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Stock { get; set; }
    public int CategoryId { get; set; } // Foreign key
    public Category Category { get; set; } // 1:N relationship
}

In this example:

  • CategoryId is the foreign key that relates Product to Category.
  • Category is a navigation property that allows access to the category associated with a product.

Migrations

Migrations are a mechanism that allows you to generate and apply changes to the database based on the entities defined in the code.

Each migration is a versioned record of changes made to the application’s data model, reflecting a set of modifications to be applied to the database (adding or removing tables, fields, constraints, etc.)

To create a migration, we use the .NET CLI (dotnet ef)

In the past, Package Manager Console commands like Add-Migration or Update-Database were used. They are currently deprecated, so if you see them, it’s old documentation.

Creating the first migration

To create the first migration, we open a terminal in the project folder and execute:

dotnet ef migrations add Initial

This generates a Migrations/ folder with three files:

  • 20250407123456_Initial.cs → Class with Up() and Down() methods.
  • SchoolContextModelSnapshot.cs → Reflects the current state of the model.
  • An additional configuration file if necessary.

What does a migration contain?

A migration in EF is a C# class that contains instructions to modify the database, both to apply changes (Up) and to undo them (Down).

The Initial class contains methods like these:

protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "Students",
        columns: table => new
        {
            Id = table.Column<int>(nullable: false)
                .Annotation("SqlServer:Identity", "1, 1"),
            Name = table.Column<string>(nullable: true),
            Email = table.Column<string>(nullable: true)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Students", x => x.Id);
        });
}

And the Down method does the opposite:

protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropTable(name: "Students");
}
  • Do not generate automatic migrations without reviewing them. Always validate what Up() and Down() will do.

Applying migrations to the database

Once the migration is created, we execute:

dotnet ef database update

This applies the Up() instructions to the database. If there is an error, the transaction is rolled back.

EF automatically creates a __EFMigrationsHistory table where it stores which migrations have been applied. This allows only the new ones to be applied and avoids duplicates.

Changes in the model and migrations

Let’s assume we add a new entity:

public class Course
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int Credits { get; set; }
}

And we add it to the DbContext:

public DbSet<Course> Courses { get; set; }

We generate the migration:

dotnet ef migrations add AddCourse

And then:

dotnet ef database update

EF will apply only the minimum and necessary changes. In this case, creating the new Courses table.

Useful migration commands

CommandDescription
dotnet ef migrations add NameCreates a new migration with detected changes.
dotnet ef database updateApplies all pending migrations.
dotnet ef migrations removeRemoves the last migration (if it has not been applied).
dotnet ef database update MigrationNameApplies up to a specific migration.
dotnet ef database update 0Reverts all migrations (returns to an empty schema).

What migrations do not do?

  • They do not handle data. If you need to insert information, you must write additional code (for example, in Up()).
  • They do not detect some “subtle” changes, such as renaming a property (it interprets it as deletion + creation).
  • They do not synchronize data in production. In many environments, these migrations must be reviewed or applied manually.