Entity Framework Core Tip: When to use .Include() vs .Select()

When querying in Entity Framework Core, both .Include() and .Select() can help shape your data — but they serve different purposes.

1. .Include() → Load Related Data (Eager Loading)

  • Tells EF Core to load related navigation properties from the database.

  • Returns full entities — including tracked states (unless you use .AsNoTracking()).

  • Best when you need all related entity details.

Good when: You need the related entities fully loaded.

Bad when: You only need a few fields (it wastes memory).

Example:

var orders = await _context.Orders
    .Include(o => o.Customer)
    .ToListAsync();

var orders = await _context.Orders .Include(o => o.Customer) .ToListAsync();

2. .Select() → Shape Data / Projection

  • Projects into custom objects (DTOs, anonymous types, etc.).

  • Loads only the fields you need, improving performance.

  • Best for lightweight queries.

Good when: You want lightweight queries & better performance.

Bad when: You actually need full entity tracking.

Example:

var orders = await _context.Orders
    .Select(o => new { o.Id, CustomerName = o.Customer.Name })
    .ToListAsync();

var orders = await _context.Orders .Select(o => new { o.Id, CustomerName = o.Customer.Name }) .ToListAsync();

Common Mistake

  • Use .Include() when you want full entities with relationships.

  • Use .Select() when you want custom shapes / DTOs and performance.

.NET 8 & EF Core 9 Example Without Database Connection

Here’s a quick in-memory simulation that demonstrates both approaches:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        using var context = new AppDbContext();

        // Seed in-memory data
        var customer = new Customer { Id = 1, Name = "John Doe" };
        var order1 = new Order { Id = 101, Customer = customer };
        var order2 = new Order { Id = 102, Customer = customer };
        context.Customers.Add(customer);
        context.Orders.AddRange(order1, order2);
        await context.SaveChangesAsync();

        // 1️⃣ Using .Include() - Full entity with relationships
        var fullOrders = await context.Orders
            .Include(o => o.Customer)
            .ToListAsync();
        Console.WriteLine("Using Include():");
        foreach (var order in fullOrders)
        {
            Console.WriteLine($"Order {order.Id}, Customer: {order.Customer.Name}");
        }

        // 2️⃣ Using .Select() - Lightweight projection
        var lightweightOrders = await context.Orders
            .Select(o => new { o.Id, CustomerName = o.Customer.Name })
            .ToListAsync();
        Console.WriteLine("\nUsing Select():");
        foreach (var order in lightweightOrders)
        {
            Console.WriteLine($"Order {order.Id}, Customer: {order.CustomerName}");
        }
    }
}

// EF Core Models
public class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
}
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// DbContext with InMemory Provider
public class AppDbContext : DbContext
{
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<Customer> Customers => Set<Customer>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseInMemoryDatabase("EFCoreDemo"); // No real DB required
    }
}

Output

Using Include():
Order 101, Customer: John Doe
Order 102, Customer: John Doe

Using Select():
Order 101, Customer: John Doe
Order 102, Customer: John Doe

Conclusion

✅ With .Include(), you get full entities and tracking — useful for updates and complete data operations.

✅ With .Select(), you only pull what you need, making your queries lighter and faster.