预先加载、延迟加载与显式加载
使用 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 反馈 — 无需本地设置。
此课程中的所有课时
- 预先加载、延迟加载与显式加载
- 原始 SQL、存储过程与插值
- 编译查询与性能
- 全局查询筛选器与拥有实体