# Entity Models & Migrations

The mental model for this phase: **a class is a table.** You write an ordinary C# class, EF Core looks at it, and figures out the table's columns, types, and primary key - mostly without you saying a word. When you later change that class, you don't reach for a SQL editor. You generate a **migration**: a small, versioned record of "the schema went from *this* to *that*," committed alongside your code and applied when you deploy.

> 📝 In [Phase 1](01-what-efcore-is.md) you built a `DbContext` with `DbSet<T>` properties. Each `DbSet<Blog> Blogs` is one table - but EF Core needs the actual `Blog` *class* to know what goes in it. That class is what we're writing now.

## A class is a table

EF Core works from **conventions** - sensible defaults it applies automatically so you write less configuration. The big ones:

- A property named `Id` (or `<TypeName>Id`, like `BlogId`) becomes the **primary key**.
- Every public property with a getter and setter becomes a **column**.
- The column's SQL type is inferred from the C# type (`int` → integer, `string` → text, `DateTime` → timestamp, and so on).
- The **table name** comes from the `DbSet` property name on your context - so `DbSet<Blog> Blogs` produces a table called `Blogs`.

Let's give our blog a `Post` entity:

```csharp
public class Post
{
    public int Id { get; set; }
    [Required, MaxLength(200)]
    public string Title { get; set; } = "";
    public string Content { get; set; } = "";
}
```

*What just happened:* EF Core reads this class and concludes: a table with `Id` as the primary key (auto-incrementing, since it's an `int` named `Id`), a `Title` text column, and a `Content` text column. `[Required, MaxLength(200)]` on `Title` is the one place we overrode a convention. The `= ""` initializers aren't an EF thing; they just keep C#'s nullable-reference warnings quiet.

And the matching `Blog`:

```csharp
public class Blog
{
    public int Id { get; set; }
    public string Url { get; set; } = "";
}
```

*What just happened:* same story - `Id` is the key, `Url` is a column. No annotations here, so EF Core uses pure conventions: `Url` becomes a text column with no length cap. We'll tighten that next, since "no length cap" is rarely what you want.

> 💡 Conventions do real work for you. You didn't declare a single column type, key, or constraint by hand - you described your data as C# types and EF Core inferred a reasonable schema. You only step in when a guess is wrong.

## Two ways to override: annotations vs the Fluent API

When the conventions aren't enough - you need a max length, a different column name, a column that *isn't* mapped at all - you have two tools.

**Data annotations** are attributes you put right on the property. They live with the model, which makes them easy to read:

| Annotation | What it does |
|---|---|
| `[Required]` | Column is `NOT NULL` |
| `[MaxLength(200)]` | Caps string/array length (e.g. `varchar(200)`) |
| `[Column("url")]` | Maps the property to a differently named column |
| `[Key]` | Marks the primary key explicitly (when convention can't guess) |
| `[NotMapped]` | Exclude this property - no column for it |

The **Fluent API** is the other option: you configure everything in code inside your `DbContext`, in an `OnModelCreating` override.

```csharp
public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs => Set<Blog>();
    public DbSet<Post> Posts => Set<Post>();

    protected override void OnModelCreating(ModelBuilder b)
    {
        b.Entity<Blog>()
            .Property(x => x.Url)
            .IsRequired()
            .HasMaxLength(200);
    }
}
```

*What just happened:* we told EF Core that `Blog.Url` is required and capped at 200 characters - the same rule `[Required, MaxLength(200)]` expresses, but written centrally instead of on the property. `ModelBuilder` is the configuration surface; `b.Entity<Blog>().Property(...)` drills down to one column and chains the rules onto it.

Why two systems? Annotations are concise and live next to the data - great for simple rules. The Fluent API is more powerful: it can express things annotations can't (composite keys, relationships, indexes, default values), and keeps entity classes free of EF-specific attributes. **If you ever configure the same thing both ways, the Fluent API wins.** A confusing "but I set `[MaxLength]`!" bug is almost always a Fluent API line quietly overriding it.

> ⚠️ Don't sprinkle both for the *same* property and hope for the best. Pick a default approach (many teams lean Fluent API for anything non-trivial) and reserve mixing for deliberate overrides you actually understand.

## Migrations: versioning your schema

You've got classes. Now you need an actual database with actual tables - and a way to *change* that schema later without losing data or hand-writing `ALTER TABLE` statements.

First, install the design-time pieces: the `Design` package powers the tooling, and the `dotnet-ef` global tool gives you the commands.

```bash
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
```

Now create your first migration:

```bash
dotnet ef migrations add InitialCreate
```

*What just happened:* EF Core compared your current model (the `Blog` and `Post` classes plus any Fluent config) against the *last* migration - since there isn't one yet, the "diff" is "create everything." It wrote a new C# migration class into a `Migrations/` folder, with two methods: `Up()` (apply the change) and `Down()` (undo it). Nothing has touched your database yet - a migration is just a plan.

Here's a peek at what that generated class looks like:

```csharp
public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Blogs",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("Sqlite:Autoincrement", true),
                Url = table.Column<string>(maxLength: 200, nullable: false)
            },
            constraints: table => table.PrimaryKey("PK_Blogs", x => x.Id));
        // ... CreateTable for "Posts" follows ...
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "Blogs");
        // ... DropTable for "Posts" ...
    }
}
```

*What just happened:* the migration describes your schema as code, not raw SQL - `Up()` creates the tables (notice `Url` came out as `maxLength: 200, nullable: false`, exactly the Fluent rule we set), and `Down()` drops them so the change is reversible. EF Core translates this to dialect-specific SQL at apply time, so the same migration can target SQLite, SQL Server, or Postgres.

Now apply it:

```bash
dotnet ef database update
```

*What just happened:* EF Core ran the `Up()` of every pending migration against your database - creating the `Blogs` and `Posts` tables for real. It also created a bookkeeping table (`__EFMigrationsHistory`) that records which migrations have applied, so next time it only runs the new ones. Run `database update` again right now and nothing happens.

If you want to *see* the SQL without touching the database, `dotnet ef migrations script` prints it - same instinct as watching the SQL EF Core generates from Phase 1.

## Migrations vs `EnsureCreated()` - don't mix them

A tempting shortcut you'll see in tutorials: `ctx.Database.EnsureCreated()`. It looks at your model and creates the schema in one shot, no migration files, no tooling.

```csharp
// Dev/prototyping only - NOT a migration.
ctx.Database.EnsureCreated();
```

*What just happened:* EF Core created the tables directly from your current model - fast and convenient for a throwaway prototype or test database. But notice what it *didn't* do: no migration created, nothing recorded in the history table, no `Down()`.

> ⚠️ `EnsureCreated()` and migrations are two different worlds that don't cooperate. `EnsureCreated()` creates the schema **once** with no concept of evolving it - there's no "add a column later." Worse, a database made by `EnsureCreated()` has no migrations history, so `database update` won't know where to start. **Pick one per database.** For anything real, that's migrations.

## A migration per change, committed and deployed

The workflow once you're rolling, worth internalizing:

1. Change a model (add a property, a new entity, a constraint).
2. `dotnet ef migrations add DescribeTheChange` - generates the diff.
3. Review the generated `Up()`/`Down()`. Did it do what you expected?
4. Commit the migration files **with the code change** - they're part of your source history.
5. On deploy, run `dotnet ef database update` (or apply the migration as part of your release).

Each model change earns its own migration - a readable, reviewable timeline of how your schema evolved, with the ability to roll forward or back deliberately.

> 💡 Treat migration files like code, because they are: reviewed in pull requests, tracked in version control, order matters. We'll cover the *production* side - applying migrations safely on a live database, handling concurrency - in [Phase 8](08-transactions-and-migrations.md). For now, the habit to build is: model change → migration → commit.

## Recap

- **A class is a table.** EF Core reads your plain C# entity classes and maps them by **convention**: `Id`/`<Type>Id` becomes the primary key, public properties become columns, types are inferred, and the table name comes from the `DbSet` name.
- Override conventions with **data annotations** (`[Required]`, `[MaxLength]`, `[Column]`, `[Key]`, `[NotMapped]`) on the property, or with the **Fluent API** in `OnModelCreating`. The Fluent API is more powerful and **wins** when both configure the same thing.
- **Migrations** version your schema. Install `Microsoft.EntityFrameworkCore.Design` and the `dotnet-ef` tool, then `dotnet ef migrations add <Name>` generates a reversible `Up()`/`Down()` class from the diff, and `dotnet ef database update` applies pending migrations.
- A migration is a **plan**, not an action - nothing hits the database until `database update`. EF Core tracks applied migrations in `__EFMigrationsHistory`.
- `EnsureCreated()` is a **dev-only** shortcut that creates schema once with no migration history - never mix it with migrations on the same database.
- Make **one migration per model change**, review it, commit it with the code, and apply it on deploy.

## Quick check

```quiz
[
  {
    "q": "You have a Blog class with an int property named Id and a public string Url. With pure EF Core conventions, what schema does EF infer?",
    "choices": ["No table at all until you add [Table] and [Key] attributes", "A Blogs table with Id as the primary key and Url as a column", "A Blog table with no primary key", "A table where only Url is mapped, because Id is reserved"],
    "answer": 1,
    "explain": "Conventions: Id becomes the primary key, public properties become columns, and the table name comes from the DbSet name (Blogs)."
  },
  {
    "q": "A property has [MaxLength(50)] in an annotation, but OnModelCreating also calls HasMaxLength(200) for it. What max length does the column get?",
    "choices": ["50, annotations take priority", "200, the Fluent API wins", "It throws an error for conflicting config", "The smaller of the two, 50"],
    "answer": 1,
    "explain": "When both configure the same thing, the Fluent API wins. That precedence is a common source of 'but I set the annotation!' confusion."
  },
  {
    "q": "What does `dotnet ef migrations add InitialCreate` do?",
    "choices": ["Immediately creates the tables in the database", "Generates a versioned migration class with Up/Down from the model diff, without touching the database", "Deletes and recreates the database from scratch", "Runs EnsureCreated() for you"],
    "answer": 1,
    "explain": "migrations add only generates the migration (a plan). Nothing reaches the database until you run dotnet ef database update."
  }
]
```
