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:
- Define the model: We create classes that represent our data (entities and DbContext).
- Configure relationships: We configure relationships and constraints through code.
- Migrations: Generate and apply migrations to create or update the database.
- 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 namedId
orClassNameId
as primary keys.Name
,Price
, andStock
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 aDbSet
that represents theProducts
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
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 relatesProduct
toCategory
.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 withUp()
andDown()
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()
andDown()
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
Command | Description |
---|---|
dotnet ef migrations add Name | Creates a new migration with detected changes. |
dotnet ef database update | Applies all pending migrations. |
dotnet ef migrations remove | Removes the last migration (if it has not been applied). |
dotnet ef database update MigrationName | Applies up to a specific migration. |
dotnet ef database update 0 | Reverts 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.