前言

上述我们简单讲解了几个小问题,这节我们再来看看如标题EF Core中多次Include导致出现性能的问题,废话少说,直接开门见山。

EntityFramework Core 3多次Include查询问题

不要嫌弃我啰嗦,我们凡事从头开始讲解起,首先依然给出我们上一节的示例类:

    public class EFCoreDbContext : DbContext
{
public EFCoreDbContext()
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(@"Server=.;Database=EFTest;Trusted_Connection=True;");
} public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> Posts { get; set; }
} public class Post
{
public int Id { get; set; }
public int BlogId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Blog Blog { get; set; }
}

接下来我们在控制台进行如下查询:

 var context = new EFCoreDbContext();

 var blog = context.Blogs.FirstOrDefault(d => d.Id == );

如上图所示,生成的SQL语句一点毛病都么有,对吧,接下来我们来查询导航属性Posts,如下:

 var context = new EFCoreDbContext();

 var blog = context.Blogs.AsNoTracking()
.Include(d => d.Posts).FirstOrDefault(d => d.Id == );

咦,不应该是INNER JOIN吗,但最终生成的SQL语句我们可以看到居然是LEFT JOIN,关键是我们对Post类中的BlogId并未设置为可空,对吧,是不是很有意思。同时通过ORDER BY对两个表的主键都进行了排序。这就是问题的引发点,接下来我们再引入两个类:

    /// <summary>
/// 博客标签
/// </summary>
public class Tag
{
public int Id { get; set; }
/// <summary>
/// 标签名称
/// </summary>
public string Name { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
} /// <summary>
/// 博客分类
/// </summary>
public class Category
{
/// <summary>
///
/// </summary>
public int Id { get; set; }
/// <summary>
/// 分类名称
/// </summary>
public string Name { get; set; }
/// <summary>
///
/// </summary>
public int BlogId { get; set; }
/// <summary>
///
/// </summary>
public Blog Blog { get; set; }
}

上述我们声明了分类和标签,我们知道博客有分类和标签,所以博客类中有对分类和标签的导航属性(这里我们先不关心关系到底是一对一还是一对多等关系),然后修改博客类,如下:

    public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> Posts { get; set; }
public List<Tag> Tags { get; set; }
public List<Category> Categories { get; set; }
}

接下来我们再来进行如下查询:

            var context = new EFCoreDbContext();

            var blogs = context.Blogs.AsNoTracking().Include(d => d.Posts)
.Include(d => d.Tags)
.Include(d => d.Categories).FirstOrDefault(d => d.Id == );

SELECT [t].[Id], [t].[Name], [p].[Id], [p].[BlogId], [p].[Content], [p].[Title], [t0].[Id], [t0].[BlogId], [t0].[Name], [c].[Id], [c].[BlogId], [c].[Name]
FROM (
SELECT TOP() [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] =
) AS [t]
LEFT JOIN [Posts] AS [p] ON [t].[Id] = [p].[BlogId]
LEFT JOIN [Tags] AS [t0] ON [t].[Id] = [t0].[BlogId]
LEFT JOIN [Categories] AS [c] ON [t].[Id] = [c].[BlogId]
ORDER BY [t].[Id], [p].[Id], [t0].[Id], [c].[Id]

此时和变更追踪没有半毛钱关系,我们看看最终生成的SQL语句,是不是很惊讶,假设单个类中对应多个导航属性,最终生成的SQL语句就是继续LEFT JOIN和ORDER BY,可想其性能将是多么的低下。那么我们应该如何解决这样的问题呢?既然是和Include有关系,每增加一个导航属性即增加一个Include将会增加一个LEFT JOIN和ORDER BY,那么我们何不分开单独查询呢,说完就开干。

            var context = new EFCoreDbContext();

            var blog = context.Blogs.AsNoTracking().FirstOrDefault(d => d.Id == );

此时我们进行如上查询显然不可取,因为直接就到数据库进行SQL查询了,我们需要返回IQueryable才行,同时根据主键查询只能返回一条,所以我们改造成如下查询:

            var context = new EFCoreDbContext();

            var blog = context.Blogs.Where(d => d.Id == ).Take();

因为接下来还需要从上下文中加载导航属性,所以这里我们需要去掉AsNoTracking,通过上下文加载指定实体导航属性,我们可通过Load方法来加载,如下:

            var context = new EFCoreDbContext();

            var blog = context.Blogs.Where(d => d.Id == ).Take();

            blog.Include(p => p.Posts).SelectMany(d => d.Posts).Load();

            blog.Include(t => t.Tags).SelectMany(d => d.Tags).Load();

            blog.Include(c => c.Categories).SelectMany(d => d.Categories).Load();

SELECT [p].[Id], [p].[BlogId], [p].[Content], [p].[Title]
FROM (
SELECT TOP() [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] =
) AS [t]
INNER JOIN [Posts] AS [p] ON [t].[Id] = [p].[BlogId] SELECT [t0].[Id], [t0].[BlogId], [t0].[Name]
FROM (
SELECT TOP() [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] =
) AS [t]
INNER JOIN [Tags] AS [t0] ON [t].[Id] = [t0].[BlogId] SELECT [c].[Id], [c].[BlogId], [c].[Name]
FROM (
SELECT TOP() [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] =
) AS [t]
INNER JOIN [Categories] AS [c] ON [t].[Id] = [c].[BlogId]

通过上述生成的SQL语句,我们知道这才是我们想要的结果,上述代码看起来有点不是那么好看,似乎没有更加优美的写法了,当然这里我只是在控制台中进行演示,为了吞吐,将上述修改为异步查询则是最佳可行方式。 比生成一大堆LEFT JOIN和ORDER BY性能好太多太多。

总结

注意:上述博主采用的是稳定版本3.0.1,其他版本未经测试哦。其实对于查询而言,还是建议采用Dapper或者走底层connection写原生SQL才是最佳,对于单表,用EF Core无可厚非,对于复杂查询还是建议不要用EF Core,生成的SQL很不可控,为了图方便,结果换来的将是CPU飙到飞起。好了,本节我们就到这里,感谢您的阅读,我们下节见。

EntityFramework Core 3多次Include导致查询性能低之解决方案的更多相关文章

  1. EntityFramework Core 2.0 Explicitly Compiled Query(显式编译查询)

    前言 EntityFramework Core 2.0引入了显式编译查询,在查询数据时预先编译好LINQ查询便于在请求数据时能够立即响应.显式编译查询提供了高可用场景,通过使用显式编译的查询可以提高查 ...

  2. EntityFramework Core 2.0执行原始查询如何防止SQL注入?

    前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...

  3. SQL Server-聚焦使用视图若干限制/建议、视图查询性能问题,你懵逼了?(二十五)

    前言 上一节我们简单讲述了表表达式的4种类型,这一系列我们来讲讲使用视图的限制,简短的内容,深入的理解,Always to review the basics. 避免在视图中使用ORDER BY 上一 ...

  4. SQL查询性能优化

    使用高效的查询 使用 EXISTS 代替 IN -- 查询A表中同时存在B表的数据 -- 慢 SELECT * FROM Class_A WHERE id IN (SELECT id FROM Cla ...

  5. EntityFramework Core查询问题集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  6. EntityFramework Core笔记:查询数据(3)

    1. 基本查询 1.1 加载全部数据 using System.Linq; using (var context = new LibingContext()) { var roles = contex ...

  7. Cookies 初识 Dotnetspider EF 6.x、EF Core实现dynamic动态查询和EF Core注入多个上下文实例池你知道有什么问题? EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)

    Cookies   1.创建HttpCookies Cookie=new HttpCookies("CookieName");2.添加内容Cookie.Values.Add(&qu ...

  8. EntityFramework Core并发导致显示插入主键问题

    前言 之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too siimple,下面我们一起来看看. ...

  9. EntityFramework Core并发导致显式插入主键问题

    前言 之前讨论过EntityFramework Core中并发问题,按照官网所给并发冲突解决方案以为没有什么问题,但是在做单元测试时发现too young,too simple,下面我们一起来看看. ...

随机推荐

  1. 小白 Python 爬虫部署 Linux

    前言 前面国庆节的时候写过一个简易的爬虫. <Python 简易爬虫实战> 还没看过的同学可以先看一下,这只爬虫主要用来爬取各个博客平台的阅读量等数据,一直以来都是每天晚上我自己手动在本地 ...

  2. vue---Excel表格导出

    一.安装依赖 npm install file-saver --save npm install xlsx --save npm install script-loader --save-dev 二. ...

  3. java架构之路-(MQ专题)RocketMQ从入坑到集群详解

    这次我们来说说我们的RocketMQ的安装和参数配置,先来看一下我们RocketMQ的提出和应用场景吧. 早在2009年,阿里巴巴的淘宝第一次提出了双11购物狂欢节,但是在2009年,服务器无法承受到 ...

  4. 心脏滴血漏洞复现(CVE-2014-0160)

    心脏滴血漏洞简述 2014年4月7日,OpenSSL发布安全公告,在OpenSSL1.0.1版本至OpenSSL1.0.1f Beta1版本中存在漏洞,该漏洞中文名称为心脏滴血,英文名称为HeartB ...

  5. Mysql用户管理及权限分配

    早上到公司,在服务器上Mysql的数据库里新建了个database,然后本地的系统里用原来连接Mysql账号admin连这个数据库.结果报错了,大概是这样子的: Access denied for u ...

  6. Feeling after reading《Jane Eyre》

    Yesterday I read and listened over the book named <Jane Eyre>, the book is very thoughtful, th ...

  7. 区块链之Hyperledger(超级账本)Fabric v1.0 的环境搭建(超详细教程)

    https://blog.csdn.net/so5418418/article/details/78355868

  8. csps模拟测试92反思

    连着挂了三天T1了. 89: SPFA$vst$数组没清空 90:调试的时候多删了一句代码 91:没开$long long$ 我真是废物. 希望以后不要犯SB错误了

  9. PHP微信授权登录用于多个域名的方法

    PHP微信授权登录用于多个域名的方法appid和 回调地址换下就好了 <pre><!DOCTYPE html><html lang="en">& ...

  10. python中程序的异常处理

    什么叫异常? 导致程序异常退出叫做异常 try...except...else 如果要抓取某种特定异常可以用except ERROR as e else:如果程序正常执行那么会执行else里面的代码 ...