在C#项目里使用EF Core查询数据时,若对一个实体同时包含多个一对多集合导航属性做贪婪加载,数据库会把主表与多个从表做联接,结果行数等于各集合条数的乘积,这种现象就是笛卡尔爆炸。查询拆分能让EF Core把查询拆为多条,分别查主体和各个集合,再在内存合并,从而避免该问题。

什么是笛卡尔爆炸
假设一个博客实体有文章集合和评论集合,使用Include同时加载两者,数据库会先让博客与文章联接,再与评论联接。若一个博客有10篇文章和20条评论,单条查询就会返回200行,大多数列为重复数据,既浪费IO也占用内存。
如何使用查询拆分
EF Core从3.0起提供AsSplitQuery方法,调用后上下文会把原查询拆成多条。下面示例展示在C#中如何书写:
using Microsoft.EntityFrameworkCore;
using System.Linq;
public class BlogService
{
private readonly AppDbContext _ctx;
public BlogService(AppDbContext ctx) => _ctx = ctx;
// 使用查询拆分避免笛卡尔爆炸
public async Task<List<Blog>> GetBlogsWithSplitAsync()
{
return await _ctx.Blogs
.Include(b => b.Posts)
.Include(b => b.Comments)
.AsSplitQuery() // 开启查询拆分
.ToListAsync();
}
}
全局开启查询拆分
若项目中多数查询都需要拆分,可在配置上下文时全局设置:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("server=.;database=demo;trusted_connection=true")
.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
}
注意事项
- 查询拆分生成的多条SQL会分别执行,可能增加数据库往返次数,需权衡网络开销。
- 在跟踪查询下,EF Core会保证拆后数据正确合并到同一实体图;非跟踪查询也支持,但注意对象引用可能不同。
- 若只加载单个集合,或集合数据量极小,不必强制使用拆分。
对比示例
| 方式 | SQL条数 | 返回行数示例 | 适用场景 |
|---|---|---|---|
| 普通Include | 1 | 200 | 单集合或小数据 |
| AsSplitQuery | 3 | 1+10+20 | 多集合大数据 |
合理运用EF Core的查询拆分,可让C#数据访问层在复杂对象图加载时保持较高性能,避免笛卡尔爆炸带来的资源浪费。