0Pricing
C# Academy · 课时

预先加载、延迟加载与显式加载

使用 Include、ThenInclude、延迟加载代理和显式 Load 调用控制相关数据的加载时机。

预先加载、延迟加载与显式加载 是 CoddyKit 上的免费 C# Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

三种加载策略

EF Core 提供三种加载相关数据的策略:预先加载(与查询一起加载)、延迟加载(访问时加载)和显式加载(按需加载)。选择合适的策略可以避免 N+1 问题和不必要的数据库往返。

使用 Include 进行预先加载

预先加载使用 Include(),在同一个 SQL 查询中通过 JOIN 加载相关数据。这是最可预测的策略,也能避免 N+1 问题。

// Single query with JOIN
var blogs = await _db.Blogs
    .Include(b => b.Posts)
    .Where(b => b.IsActive)
    .ToListAsync();

// SQL: SELECT * FROM Blogs LEFT JOIN Posts ON ...

使用 ThenInclude 加载嵌套关系

ThenInclude 可以沿导航图继续深入。您可以在一次查询中加载多个层级。

var orders = await _db.Orders
    .Include(o => o.Customer)
    .Include(o => o.Lines)
        .ThenInclude(l => l.Product)
            .ThenInclude(p => p.Category)
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync();

筛选式 Include

EF Core 5+ 支持在 Include 中筛选相关数据。这样可以加载部分子实体,而无需执行单独的查询。

// Load only published posts for each blog
var blogs = await _db.Blogs
    .Include(b => b.Posts.Where(p => p.IsPublished)
                         .OrderByDescending(p => p.PublishedAt)
                         .Take(5))
    .ToListAsync();

设置延迟加载

延迟加载会在导航属性首次被访问时自动加载它们。安装 Microsoft.EntityFrameworkCore.Proxies 包并调用 UseLazyLoadingProxies() 即可启用此功能。

// 1. Install package: dotnet add package Microsoft.EntityFrameworkCore.Proxies
// 2. Enable in DbContext setup:
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlite(connStr)
       .UseLazyLoadingProxies());

// 3. Mark navigation properties as virtual:
public class Blog
{
    public int Id { get; set; }
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

延迟加载实战(以及 N+1 陷阱)

延迟加载很方便,但在循环中使用时存在风险——每次访问都会触发新的 SQL 查询,从而导致 N+1 问题。在列表场景中,请始终优先使用预先加载。

var blogs = await _db.Blogs.ToListAsync(); // 1 query

foreach (var blog in blogs)
{
    // BAD: each access fires a new query!
    var count = blog.Posts.Count; // N queries
    Console.WriteLine(count);
}

// FIX: Use eager loading
var blogs2 = await _db.Blogs.Include(b => b.Posts).ToListAsync();

显式加载

显式加载允许您按需加载导航属性,可以使用 Entry().Collection().LoadAsync() 或 Entry().Reference().LoadAsync()。当您需要根据条件加载相关数据时,这种方式非常有用。

var blog = await _db.Blogs.FindAsync(1); // no posts loaded

// Load posts only when needed
if (needsPosts)
{
    await _db.Entry(blog!)
             .Collection(b => b.Posts)
             .LoadAsync();
}

// Load single reference
var post = await _db.Posts.FindAsync(99);
await _db.Entry(post!)
         .Reference(p => p.Blog)
         .LoadAsync();

使用查询筛选器进行显式加载

调用 LoadAsync() 之前,可以使用 .Query() 筛选显式加载的数据,从而减少返回的数据量。

var blog = await _db.Blogs.FindAsync(1);

// Only load posts published this year
await _db.Entry(blog!)
         .Collection(b => b.Posts)
         .Query()
         .Where(p => p.PublishedAt.Year == DateTime.UtcNow.Year)
         .LoadAsync();

Console.WriteLine(blog!.Posts.Count);

针对大型 Include 的拆分查询

在一个查询中加载多个集合会导致笛卡尔积爆炸(行数成倍增加)。请使用 AsSplitQuery() 为每个集合发出单独的查询,然后在内存中合并结果。

var orders = await _db.Orders
    .Include(o => o.Lines)
    .Include(o => o.Shipments)
    .AsSplitQuery()  // 3 separate SELECTs instead of one giant JOIN
    .ToListAsync();

// Or configure globally:
opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);

使用投影避免获取过多数据

不要加载包含全部关系的完整实体,而是使用 Select 投影到 DTO。这样可以在一个经过优化的查询中准确获取所需数据。

var summaries = await _db.Blogs
    .Select(b => new BlogSummaryDto
    {
        Name      = b.Name,
        PostCount = b.Posts.Count(p => p.IsPublished),
        LatestPost = b.Posts
            .Where(p => p.IsPublished)
            .OrderByDescending(p => p.PublishedAt)
            .Select(p => p.Title)
            .FirstOrDefault()
    })
    .ToListAsync();
// No Include needed - translated to subqueries

实战:选择正确的策略

在实际应用中,请参考以下决策指南:

  • 预加载 + Include:始终需要相关数据的场景,例如列表页
  • 投影:只读视图、API 响应——效率最高
  • 显式加载:根据应用逻辑有条件地加载
  • 延迟加载:在 Web 应用中应避免使用;仅适用于数据集较小的桌面应用或脚本

快速检查

在延迟加载的上下文中,N+1 问题是什么?

回顾:加载策略

要点:

  • 预加载(Include):通过一个查询加载数据——最适合访问模式可预测的场景
  • 筛选 Include:只加载部分子项——需要 EF Core 5+
  • 延迟加载:使用方便但存在风险——避免在循环中使用(N+1 问题)
  • 显式加载:按需加载,并进行细粒度控制
  • 使用 Select 投影:最适合只读场景
  • 拆分查询:通过多个集合 Include 避免笛卡尔积爆炸

常见问题解答

「预先加载、延迟加载与显式加载」课时是免费的吗?

是的 — 「预先加载、延迟加载与显式加载」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「预先加载、延迟加载与显式加载」这节课中我会学到什么?

使用 Include、ThenInclude、延迟加载代理和显式 Load 调用控制相关数据的加载时机。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「预先加载、延迟加载与显式加载」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 预先加载、延迟加载与显式加载
  2. 原始 SQL、存储过程与插值
  3. 编译查询与性能
  4. 全局查询筛选器与拥有实体
← 返回 C# Academy