Lesson 17 of 45 intermediate

EF Core Basics and Relationships

How classes become tables, and how tables point at each other

Guess first

Pick an answer. You will see if you were right at the end of the lesson.

What does DeleteBehavior.Cascade on the Order to OrderItem relationship do?

Open interactive version (quiz + challenge)

Real-world analogy

Think of a school office. The class register is the DbContext: the teacher marks changes on it during one lesson, and the office files them in one go at the end. A student card carries the number of their class, which is the foreign key. The card also lets you walk to the class and back, which is the navigation property. Close a class and every student card tied to it may be thrown away too, which is cascade delete.

What is it?

Entity Framework Core (EF Core) is an object-relational mapper. You write classes, and it maps them to tables and turns LINQ into SQL. A DbContext holds your DbSet properties and tracks the changes you make. Relationships come from foreign keys: EF Core reads your properties by convention, and you correct it with the Fluent API in OnModelCreating. Migrations record each schema change as code, so every environment can reach the same shape.

Real-world relevance

On a real project the first migration creates the tables, and each later feature adds a small migration that goes through code review like any other change. The painful surprises are usually relationships: a foreign key property with a name EF Core does not recognise, or a cascade delete that removes order history when someone deletes a customer. Both are easy to catch by reading the generated migration before you apply it.

Key points

Quick check

Answer from memory — scrolling up first makes it stick less.

You applied a migration to the shared test database and then spot a typo in it. What is the safe fix?

Which line describes a many-to-many relationship in EF Core?

Code example

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public List<Order> Orders { get; set; } = new();
}

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; } = null!;
    public List<OrderItem> Items { get; set; } = new();
}

public class OrderItem
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public string Sku { get; set; } = "";
}

public class ShopContext : DbContext
{
    public ShopContext(DbContextOptions<ShopContext> options) : base(options) { }
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<Order> Orders => Set<Order>();
    protected override void OnModelCreating(ModelBuilder b)
    {
        b.Entity<Order>().HasOne(o => o.Customer).WithMany(c => c.Orders)
            .HasForeignKey(o => o.CustomerId).OnDelete(DeleteBehavior.Restrict);
        b.Entity<Order>().HasMany(o => o.Items).WithOne()
            .HasForeignKey(i => i.OrderId).OnDelete(DeleteBehavior.Cascade);
    }
}

Line-by-line walkthrough

  1. 1. Customer holds a list of Orders, and Order holds a list of OrderItems. That is the one-to-many chain.
  2. 2. Order has both CustomerId (the column) and Customer (the object you walk to). OrderItem only has the column.
  3. 3. ShopContext lists two DbSets. OrderItem has no DbSet, but EF Core still finds it through the Items navigation.
  4. 4. OnModelCreating spells out the relationships the conventions could only guess at.
  5. 5. Customer to Order uses Restrict, so the database refuses to delete a customer who still has orders.
  6. 6. Order to OrderItem uses Cascade, so deleting an order removes its own items with it.

Spot the bug

public class Customer { public int Id { get; set; } }

public class Order
{
    public int Id { get; set; }
    public int OwnerId { get; set; }
    public Customer Customer { get; set; } = null!;
}

// The Orders table now has OwnerId and CustomerId.
// Saved orders show a CustomerId, and OwnerId is always 0.
Need a hint?
Which foreign key name does EF Core look for when the navigation property is called Customer?
Show answer
EF Core expects the foreign key to be named CustomerId, so it does not link OwnerId to Customer. It adds a hidden shadow column CustomerId and leaves OwnerId as a plain number column. Fix: rename OwnerId to CustomerId, or tell EF Core with HasForeignKey(o => o.OwnerId).

Explain like I'm 5

Your classes are drawings, and the database is a set of tables. EF Core is the helper who turns each drawing into a table and keeps the strings between them tied correctly. A migration is a note that says "we changed the tables today".

Fun fact

You do not have to register OrderItem as a DbSet. EF Core finds it by following the Items navigation from Order, and it still gets its own table.

Hands-on challenge

Say it out loud in 30 seconds: "How does EF Core model a one-to-many relationship, and how do you change the database safely afterwards?" Then check: did you mention the foreign key and navigation property, the Fluent API or conventions, and migrations that you add and never edit once applied?

More resources

Open interactive version (quiz + challenge) Download the whole course as PDF / Word

Lock it in

← Back to course: .NET + React Playbook