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.