EF Core 11 优化拆分查询:运行时间缩短、内存分配减少!
2026/7/13 3:16:43 网站建设 项目流程

EF Core 11 让拆分查询更快:运行时间缩短、内存分配减少!

2026 年 6 月 8 日,这是一件值得开发者关注的技术优化事件。如果你在代码库中使用了 `AsSplitQuery`,EF Core 11 会给你带来查询速度提升的惊喜。

目录

问题是什么?

有什么变化?

基准测试

资源

默认情况下,EF Core 会在一个查询中加载所有内容。若使用 `Include` 加载一个集合,会使用 JOIN 操作,对集合使用 JOIN 会为每个子项复制父行,连接内容越多情况越糟。`AsSplitQuery` 可解决该问题,EF 会为根实体执行一个查询,为每个额外的集合执行一个查询。不过这并非万能之策,还可能导致更多往返操作,且因不是原子操作(涉及多个事务),根实体和子项之间的状态可能发生变化。更多信息请见: 。

问题是什么?

以下是 原始问题 的设置:一个引用导航属性,一个集合导航属性。

var result = context.Blogs .Include(x => x.BlogType) // 引用导航属性 .Include(x => x.Posts) // 集合导航属性 .AsSplitQuery() .ToList();

在 EF Core 10 及之前的版本中,这会生成两个查询。

SELECT [b].[Id], [b].[BlogDetailId], [b].[BlogTypeId], [b].[Name], [b0].[Id], [b0].[Type] FROM [Blogs] AS [b] LEFT JOIN [BlogType] AS [b0] ON [b].[BlogTypeId] = [b0].[Id] ORDER BY [b].[Id], [b0].[Id] SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id], [b0].[Id] FROM [Blogs] AS [b] LEFT JOIN [BlogType] AS [b0] ON [b].[BlogTypeId] = [b0].[Id] INNER JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId] ORDER BY [b].[Id], [b0].[Id]

看看第二个查询,即获取文章的查询。其中出现 `BlogType` 是不必要的,它连接了该表,选择了其 `Id` 并按其排序。要将 `Post` 与其 `Blog` 匹配,博客的主键就足够了。而且,引用导航属性越多情况越糟,若有五个引用导航属性使用 `Include`,每个集合查询都会附带五个 JOIN 操作和五个额外的 ORDER BY 列,数据库执行这些操作却丢弃结果,查询计划也会受影响。

有什么变化?

从预览版 3 开始,EF Core 11 会从集合查询中移除引用导航属性。现在,第二个查询就像手动编写的一样。

SELECT [p].[Id], [p].[BlogId], [p].[Title], [b].[Id] FROM [Blogs] AS [b] INNER JOIN [Post] AS [p] ON [b].[Id] = [p].[BlogId] ORDER BY [b].[Id]

基准测试

会在博客文章末尾提供完整设置的链接。以下是使用的一个小设置,可在上面链接的 Github 问题中找到几乎相同的内容。

免责声明:对比了 .NET 10 中的 EF 10 和 .NET 11 中的 EF 11。需说明的是,并非所有性能提升都一定仅来自 EF 11,因为在基准测试中改变了两个变量。理论上,也可能完全是从 .NET 10 升级到 .NET 11 的原因,但这种可能性不大。

[MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net10_0)] [SimpleJob(RuntimeMoniker.Net11_0)] public class SplitQueryBenchmarks { private SqliteConnection _connection = null!; private DbContextOptions<BloggingContext> _options = null!; [GlobalSetup] public void Setup() { _connection = new SqliteConnection("Data Source=:memory:"); _connection.Open(); _options = new DbContextOptionsBuilder<BloggingContext>() .UseSqlite(_connection) .Options; using var context = new BloggingContext(_options); context.Database.EnsureCreated(); Seeder.Seed(context, blogCount: 5_000, postsPerBlog: 5); } [GlobalCleanup] public void Cleanup() => _connection.Dispose(); [Benchmark] public List<Blog> SplitQuery() { using var context = new BloggingContext(_options); return BlogQuery(context).AsSplitQuery().ToList(); } private static IQueryable<Blog> BlogQuery(BloggingContext context) => context.Blogs .AsNoTracking() .Include(b => b.Author) .Include(b => b.Category) .Include(b => b.Series) .Include(b => b.Posts); }

结果如下:

方法运行时平均值误差标准差中位数Gen0Gen1Gen2分配内存
SplitQuery.NET 10.068.53 ms1.358 ms2.742 ms67.54 ms4375.00001000.0000375.000033.35 MB
SplitQuery.NET 11.062.07 ms1.213 ms2.788 ms61.10 ms4111.11111333.3333444.444430.15 MB

可以看到,运行时间更短,内存分配也更少!

资源

本文的源代码:点击查看

所有的示例代码都托管在这个仓库中:点击查看

Github 问题:https://github.com/dotnet/efcore/issues/29182

你可能感兴趣的相关文章

EF Core 6 - 新特性

EF Core 6 即将推出,看起来非常有前景。在这篇博客文章中,将介绍即将推出的新特性。2021 年 7 月 12 日 阅读时长:3 分钟 EF CoreC#.NET 6EF Core 6

我的 EF LINQ to SQL 查询安全吗?

是否曾自问,EF LINQ 查询是否容易受到 SQL 注入攻击?是因为从文本字段中查询用户数据,还是直接将 API 接收到的数据用于数据库查询?2024 年 6 月 17 日 阅读时长:3 分钟 C#.NETEntity FrameworkEF

.NET 11 中的 Zstandard 压缩

.NET 已经有 DeflateStream、GZipStream、ZLibStream 和 BrotliStream 有一段时间了。在 .NET 11 中,一个新成员加入了:ZstandardStream。现在可以在 .NET 中使用 “Zstd” 了。2026 年 5 月 18 日 阅读时长:2 分钟 .NET.NET 11

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询