Learn and Teach Coding
.NETIntermediateMay 15, 2024 · 11 min read

Entity Framework Core Relationships Explained

One-to-one, one-to-many, and many-to-many relationships in EF Core, with fluent configuration and loading strategies.

Relationships are where most EF Core confusion lives. Let's model each type and configure it explicitly.

One-to-many

A blog has many posts; each post belongs to one blog:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public List<Post> Posts { get; set; } = new();
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int BlogId { get; set; }      // foreign key
    public Blog Blog { get; set; } = null!;
}

EF Core infers this from the navigation properties, but you can be explicit:

modelBuilder.Entity<Post>()
    .HasOne(p => p.Blog)
    .WithMany(b => b.Posts)
    .HasForeignKey(p => p.BlogId);

Many-to-many

Since EF Core 5 you no longer need an explicit join entity:

public class Post
{
    public List<Tag> Tags { get; set; } = new();
}

public class Tag
{
    public List<Post> Posts { get; set; } = new();
}

EF Core creates the join table for you.

Loading strategies

Watch out for the N+1 problem: lazy loading inside a loop fires one query per row. Prefer eager loading with Include when you know you need the related data.

// Eager — one query with a JOIN
var blogs = await db.Blogs.Include(b => b.Posts).ToListAsync();

// Explicit — load on demand
await db.Entry(blog).Collection(b => b.Posts).LoadAsync();

Model the relationship, configure it with the Fluent API when inference isn't enough, and pick a loading strategy that matches how you'll use the data.

Related tutorials