【Entity Framework】聊聊EF中复杂查询运算符

简介: 【Entity Framework】聊聊EF中复杂查询运算符

一、概述

语言集成查询 (LINQ) 包含许多用于组合多个数据源或执行复杂处理的复杂运算符。 并非所有 LINQ 运算符都会在服务器端进行适当转换。 有时,采用一种形式的查询会转换为服务器,但如果采用另一种形式,即使结果相同,也不会转换。本文将介绍部分复杂运算符及其支持的变体。


二、联接

借助LINQ Join运算符,可根据每个源的键选择器连接两个数据源,并在键匹配时生成值的元组。该运算符在关系数据库中自然而然地转换为INNER JOIN。虽然LINQ Join具有外部和内部键选择器,但数据库只需要一个联接条件。因此EF Core通过比较外部键选择器和内部键选择器是否相等,来生成联接条件。


var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };

下面语句生成SQL

 [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]
SELECT

此外,如果键选择器是匿名类型,则 EF Core 会生成一个联接条件,以比较组件是否相等。

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

三、GroupJoin

借助LINQ GroupJoin运算符,可以采用与Join类似的方式连接两个数据源,但它会创建一组内部值,用于匹配外部元素。执行与以下示例类似的查询将生成Blog和IEnumerable<Post>的结果。由于数据库(特别是关系数据库)无法表示一组客户端对象,因此在许多情况下,GroupJoin 不会转换为服务器。 它需要从服务器获取所有数据来进行 GroupJoin,无需使用特殊选择器(下面的第一个查询)。 但如果选择器限制选定的数据,则从服务器提取所有数据可能会导致出现性能问题(下面的第二个查询)。 这就是 EF Core 不转换 GroupJoin 的原因。

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };


四、SelectMany

借助 LINQ SelectMany 运算符,可为每个外部元素枚举集合选择器,并从每个数据源生成值的元组。 在某种程度上,它是没有任何条件的联接,因此每个外部元素都与来自集合源的元素连接。 根据集合选择器与外部数据源的关系,SelectMany 可在服务器端转换为各种不同的查询。

4.1/集合选择器不引用外部

如果集合选择器不引用外部源中的任何内容,则结果是这两个数据源的笛卡尔乘积。 它在关系数据库中转换为 CROSS JOIN

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };

生成SQL语句如下:

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

4.2/集合选择器引用 where 子句中的外部

如果集合选择器具有引用外部元素的 where 子句,则 EF Core 会将其转换为数据库联接,并将谓词用作联接条件。 在对外部元素使用集合导航作为集合选择器时,通常会出现这种情况。 如果外部元素的集合为空,则不会为该外部元素生成任何结果。 但如果在集合选择器上应用 DefaultIfEmpty,则外部元素将与内部元素的默认值连接。 由于这种区别,应用 DefaultIfEmpty 时,如果缺少 DefaultIfEmpty 和 LEFT JOIN,此类查询会转换为 INNER JOIN。

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };

生成SQL语句如下:

SESELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]


五、GroupBy

LINQ GroupBy运算符创建IGrouping<TKey,TElement>类型的结果,其中TKey和TElement可以是任意类型,此外,IGrouping实现了IEnumerable<IElement>,这意味着可在分组后使用任意LINQ运算符来对其进行组合。由于任何数据库结构都无法表示IGrouping,因此在大多数情况下GroupBy运算符不会进行任何转换。聚合运算符应用于返回标量的每个组时,该运算符可在关系数据库中转换为 SQL GROUP BY。 SQL GROUP BY 也会受到限制。 它要求只按标量值进行分组。 投影只能包含分组键列或对列应用的任何聚合。 EF Core 标识此模式并将其转换为服务器,如以下示例中所示:

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core 还会转换符合以下条件的查询:分组的聚合运算符出现在 Where 或 OrderBy(或其他排序方式)LINQ 运算符中。 它在 SQL 中将 HAVING 子句用于 where 子句。 在应用 GroupBy 运算符之前的查询部分可以是任何复杂查询,只要它可转换为服务器即可。 此外,将聚合运算符应用于分组查询以从生成的源中移除分组后,可以像使用任何其他查询一样,在它的基础上进行组合。

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };

生成SQL语句:

SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

EF Core 支持的聚合运算符如下所示 图片.png

可能支持其他聚合运算符。 检查提供程序文档以获取更多函数映射。


尽管没有表示 IGrouping 的数据库结构,但在某些情况下,EF Core 7.0 和更新版本可以在从数据库返回结果后创建分组。 这类似于 Include运算符在包含相关集合时的工作方式。 以下 LINQ 查询使用 GroupBy 运算符按 Price 属性的值对结果进行分组。

var query = context.Books.GroupBy(s => s.Price);
• 1

SQL语句执行:

SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

在这种情况下,GroupBy 运算符不会直接转换为 SQL 中的 GROUP BY 子句,EF Core 会在从服务器返回结果后创建分组。


六、Left Join

虽然 Left Join 不是 LINQ 运算符,但关系数据库具有常用于查询的 Left Join 的概念。 LINQ 查询中的特定模式提供与服务器上的 LEFT JOIN 相同的结果。 EF Core 标识此类模式,并在服务器端生成等效的 LEFT JOIN。 该模式包括在两个数据源之间创建 GroupJoin,然后通过对分组源使用 SelectMany 运算符与 DefaultIfEmpty 来平展分组,从而在内部不具有相关元素时匹配 null。 下面的示例显示该模式的样式及其生成的内容。

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };


七、总结

以上模式在表达式树中创建复杂的结构。因此,EF Core要求在紧随运算符的步骤中将GroupJoin运算符的分组结果平展。即使使用GroupJoin-DefaultIfEmpty-SelectMany,但采用其他的模式,也不能将其标识为Left Join

相关实践学习
MySQL数据库快速部署实践
本场景主要介绍如何在一台配置了CentOS 7.7版本的ECS实例(云服务器)上安装mysql,执行mysql的常用操作,学习基本的SQL语句。
目录
相关文章
|
并行计算 安全 Java
C# .NET面试系列四:多线程
<h2>多线程 #### 1. 根据线程安全的相关知识,分析以下代码,当调用 test 方法时 i > 10 时是否会引起死锁? 并简要说明理由。 ```c# public void test(int i) { lock(this) { if (i > 10) { i--; test(i); } } } ``` 在给定的代码中,不会发生死锁。死锁通常是由于两个或多个线程互相等待对方释放锁而无法继续执行的情况。在这个代码中,只有一个线程持有锁,且没有其他线程参与,因此不
917 3
|
存储 SQL 关系型数据库
MySQL 中character_set_server 和collation_server
在MySQL中,`character_set_server` 和 `collation_server` 是两个重要的系统变量,它们分别用于定义服务器级别的字符集和排序规则。 1. **character_set_server**: * 这个变量定义了MySQL服务器使用的默认字符集。字符集是一组字符的编码方式,它决定了如何在数据库中存储和检索字符数据。 * 例如,`utf8` 是一个常用的字符集,它支持大部分Unicode字符,包括中文、英文、阿拉伯文等。 * 你可以通过以下SQL命令查看当前的 `character_set_server` 设置: ``` sql`SHOW V
1997 1
|
2月前
|
机器学习/深度学习 人工智能 算法
基于 YOLOv8 的学生课堂行为检测(举手、看书、写作业、玩手机)-完整项目源码
基于YOLOv8的学生课堂行为检测系统,实现举手、听讲、玩手机等行为的实时识别。项目包含完整源码、预训练模型与标注数据集,结合PyQt5开发可视化界面,支持图片、视频、摄像头多模式输入。通过构建高质量行为数据集并优化模型训练,系统可稳定部署于智慧教学场景,助力课堂状态分析与教学评估,推动AI在教育领域的落地应用。
341 0
基于 YOLOv8 的学生课堂行为检测(举手、看书、写作业、玩手机)-完整项目源码
|
存储 缓存 数据库连接
Entity Framework Core 跨数据库查询超厉害!多数据库连接最佳实践,让你的开发更高效!
【8月更文挑战第31天】在现代软件开发中,跨数据库查询是常见需求。Entity Framework Core(EF Core)作为强大的ORM框架,支持多种方法实现这一功能。本文介绍了在EF Core中进行跨数据库查询的最佳实践,包括:理解数据库上下文、使用多个上下文进行查询、处理数据库连接与事务,以及性能优化策略。通过创建独立的数据库上下文如`UserContext`和`OrderContext`,并在业务逻辑中同时使用它们,可以轻松实现跨库查询。此外,利用`TransactionScope`可确保事务一致性,从而提高系统的可靠性和效率。
1204 0
|
缓存 NoSQL Java
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
缓存与分布式锁、Redisson分布式锁、缓存数据一致性【必须满足最终一致性】
924 16
谷粒商城笔记+踩坑(12)——缓存与分布式锁,Redisson+缓存数据一致性
|
存储 安全 物联网
C# 在物联网 (IoT) 应用中的应用
本文介绍了C#在物联网(IoT)应用中的应用,涵盖基础概念、优势、常见问题及其解决方法。重点讨论了网络通信、数据处理和安全问题,并提供了相应的代码示例,旨在帮助开发者更好地利用C#进行IoT开发。
670 3
|
开发框架 .NET 数据库
全面掌握Entity Framework Core高级查询技巧:详解自定义函数与聚合函数的实现方法及应用场景,附带完整代码示例与最佳实践指导
【8月更文挑战第31天】在使用 Entity Framework Core (EF Core)进行数据访问时,常需执行复杂的数据库操作,如自定义函数调用或使用聚合函数汇总数据。EF Core 支持在 LINQ 查询中使用自定义与聚合函数,满足高级查询需求。
318 1
|
存储 算法 搜索推荐
海量数据处理
海量数据处理
460 0
|
监控 Java 测试技术
🔥Python性能测试大揭秘!JMeter与Locust如何让你的应用跑出新速度🚀
【8月更文挑战第4天】在软件开发中,性能测试确保应用稳定高效。JMeter与Locust是两大性能测试利器。JMeter源自Apache,基于Java,支持多协议测试,提供丰富测试场景与直观结果展示。Locust专为Python打造,采用Python脚本定义测试场景,支持分布式测试与实时监控。选择时考虑应用语言与团队技能。正确的测试策略结合合适工具,助您的应用性能更上一层楼。🚀
335 1
|
C++
Visual Studio(clang)代码格式化
Visual Studio(clang)代码格式化
283 0

热门文章

最新文章