C# LINQ查询

news2024/11/28 14:31:56

一、什么是LINQ

LINQ是Language-Integrated Query的缩写,它可以视为一组语言和框架特性的集合。LINQ可以对本地对象集合或远程数据源进行结构化的类型安全的查询操作。LINQ支持查询任何实现了IEnumerable<T>接口的集合类型,无论是数组、列表还是XML DOM,乃至SQL Server数据库中的数据表这种远程数据源都可以查询。LINQ具有编译时类型检查动态查询组合这两大优点。

二、LINQ的基本用法

2.1 流式语法

流式语法是编写LINQ表达式的最基础同时也是最灵活的方式。它允许我们使用查询运算符链构造更复杂的查询。

首先来介绍几个最常见的查询运算符WhereOrderBySelectWhere会根据输入的条件筛选序列;OrderBy运算符根据输入的序列生成一个排序后的版本;Select将输入序列中的每一个元素按给定的Lambda表达式进行转换或映射。

string[] names = {"Tom","John","Mary","LiHua","ZhangSan"};
IEnumerable<string> res = names
	.Where(e => e.Contains("o"))
	.OrderBy(e => e.Length)
	.Select(e=>e.ToUpper());

foreach (var item in res)
{
	Console.WriteLine(item);
}
// 输出结果:  
// TOM  
// JOHN

这些查询运算符实际上是在Enumerable类中对IEnumerable<>的扩展方法(以Where为例)。通过接收一个委托来进行元素的筛选。

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

需要注意的是:查询运算符绝不会修改输入序列,相反,它会返回一个新序列。这种设计是符合函数式编程规范的,而LINQ就是起源自函数式编程。

下面再介绍一些其他的查询运算符
Take运算符将输出前x个元素,而丢弃其他元素

// Take运算符  
var res1 = names.Take(3);  
foreach (var item in res1)  
{  
    Console.WriteLine(item);  
}  
// 输出结果:  
// Tom  
// John  
// Mary

Skip运算符会跳过集合中的前x个元素而输出剩余的元素

// Skip运算符  
var res2 = names.Skip(3);  
foreach (var item in res2)  
{  
    Console.WriteLine(item);  
}  
// 输出结果:  
// LiHua  
// ZhangSan

Reverse运算符会将集合中的所有元素反转

// Reverse运算符  
var res3 = names.Reverse();  
foreach (var item in res3)  
{  
    Console.WriteLine(item);  
}  
// 输出结果:  
// ZhangSan  
// LiHua  
// Mary  
// John  
// Tom

FirstLastElementAt可以返回对应位置的元素

var item1 = names.First(); // Tom  
var item2 = names.Last(); // ZhangSan  
var item3 = names.ElementAt(3); // LiHua

Count可以返回元素总数,MinMax可以分别返回最小值和最大值

var count = names.Count();// 5  
var min = names.Min();// John  
var max = names.Max();// ZhangSan

Contains可以返回序列是否包含某个元素,Any可以根据条件判断序列是否存在指定元素

var isContains = names.Contains("ZhangSan"); // True  
var isAny = names.Any(e => e.Equals("John")); // True

Concat运算符会将一个输入序列附加到另一个序列后面,Union运算符除了附加之外还会去掉其中重复的元素

int[] seq1 = {1, 2, 3};  
int[] seq2 = {3, 4, 5};  
  
var seq3 = seq1.Concat(seq2);// 1,2,3,3,4,5  
var seq4 = seq1.Union(seq2);// 1,2,3,4,5

2.2 查询表达式

C#为LINQ查询提供了一种简化的语法结构,称为查询表达式。这种语法就像是在C#中内嵌SQL。

下面将前面讲过的常见查询运算符转换为查询表达式形式

IEnumerable<string> query =
	from name in names
	where name.Contains("o")
	orderby name.Length
	select name.ToUpper();

foreach (var item in query)
{
	Console.WriteLine(item);
}

// 输出结果:
// TOM
// JOHN

其中from子句的作用是声明范围变量,和foreach很像。这一点与SQL语句有所不同。LINQ中,变量必须在声明后才能使用,而SQL中SELECT子句可以在FROM子句定义之前直接引用表的别名。

三、延迟执行

大部分查询运算符的一个重要性质是它们并非在构造时执行,而是在枚举(即在枚举器上调用MoveNext)时执行。
比如下面这个例子:

var numbers = new List<int>() {1, 2, 3};  
var res = numbers.Select(e => e * 10);  
numbers.Add(4);  
  
foreach (var item in res)  
{  
    Console.Write(item+" ");  
}  
// 输出结果:  
// 10 20 30 40

从直觉上讲,这段代码的输出结果应该是10 20 30,毕竟numbers数组是在查询结束后才发生的改变。但事实并非如此。在查询语句创建结束后,向列表中新添加的数字也出现在了查询结果中。这是因为查询逻辑只有在foreach执行时才会生效,即延迟执行。

几乎所有的标准查询运算符都具有延迟执行的能力,但以下运算符除外:

  • 返回单个元素或标量值的运算符,例如FirstCount
  • 转换运算符:ToArrayToListToDictionaryToLookupToHashSet

以上这些运算符都会立即执行并返回结果。

延迟执行是一个很重要的特性。因为它将查询的创建和查询的执行进行了解耦。这使得查询可以分多个步骤进行创建,尤其适用于创建数据库查询。

3.1 重复执行

延迟执行也带来了一些问题,比如重复执行。当重复枚举时,延迟执行的查询也会重复执行。比如下面这个例子:

var numbers = new List<int>() {1, 2, 3};
var res = numbers.Select(e => e * 10);
numbers.Add(4);

foreach (var item in res)
{
	Console.Write(item+" ");
}
// 输出结果:
// 10 20 30 40
Console.WriteLine("");

numbers.Add(5);
foreach (var item in res)
{
	Console.Write(item+" ");
}
// 输出结果:
// 10 20 30 40 50

可以看到,虽然第一次枚举时,查询已经执行了。但改变数组后再次枚举,查询执行的结果也会随之改变。也就是说,即便查询执行后也不会缓存此时执行的结果。每进行一次枚举,都会重复执行查询。对于一些计算密集型查询(或依赖远程数据库的查询),这会带来不必要的浪费。

要避免重复执行,可以通过ToListToArray缓存执行结果。

3.2 捕获变量

延迟执行的另一个问题是,如果Lambda表达式捕获了外部变量,那么该变量也会等到执行时决定。

var numbers = new List<int>() {1, 2, 3};  
int factor = 10;  
var res = numbers.Select(e => e * factor);  
factor = 20;  
  
foreach (var item in res)  
{  
    Console.Write(item+" ");  
}  
// 输出结果:  
// 20 40 60

3.3 延迟执行原理

LINQ查询实际上使用了装饰模式。查询运算符通过返回装饰器序列来提供延迟执行的功能。装饰器序列不同于一般的集合类(如数组或链表),它(一般)并没有存储元素的后台结构。而是包装了一个在运行时才会生成的序列,并永久维护其依赖关系。当向装饰器序列请求数据时,它就不得不向被包装的输入序列请求数据。如下图中,只有当枚举lessThanTen时,才开始真正通过Where装饰器对数组执行查询。

当采用查询运算符链时,生成的装饰器将层层嵌套,像套娃一样

四、解释型查询与本地查询

LINQ提供了两种平行的架构:针对本地对象集合的本地查询,以及针对远程数据源的解释型查询。前面我们介绍的都是本地查询的架构。

本地查询主要针对实现了IEnumerable<T>的集合类型进行操作。本地查询会(默认)使用Enumerable类中的查询运算符,进而生成链式装饰器序列。其接受的委托(不论是使用查询语法、流式语法,还是通常的委托)都会完全编译为中间语言代码。

解释型查询是描述性的。它操作的序列实现了IQuerable<T>接口,并且其查询运算符是定义在Queryable类中的。它们会在运行时生成表达式树,并进行解释。这些表达式树可以转换为其他语言,例如它可以转换为SQL查询,这样就可以使用LINQ查询数据库了。

4.1 解释型查询工作机制

下面是一段使用EF Core编写一个解释型LINQ查询从数据库中找到所有名字中含有字母“a”的客户的代码

NutshellContext类的定义如下

首先编译器会将上面的查询语法转换为流式语法(与本地查询一致)

其次,编译器会进一步解析上述查询中的运算符方法,而这也是本地查询和解释型查询的区别所在,解释型查询会解析为Queryable类中的方法,而本地查询会解析为Enumerable类中的方法。而上面的代码之所以会解析为Queryable类中的方法,是因为dbContext.Customers是一个类型为DbSet<T>的变量,它实现了IQueryable<T>接口。

接下来编译器将根据Queryable.Where的参数将Lambda表达式n=>n.Name.Contains("a")转换为表达式树(OrderBySelect同理)。EF Core再将表达式树延迟转换为SQL语句。

需要注意的是,不同于本地查询那种层层嵌套的执行方式,解释型查询会遍历所有表达式并直接生成一个独立的清单(SQL语句),并在执行后将结果返回消费者。相当于只有一层装饰器。

五、参考资料

[1].《C#8.0核心技术指南》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166486.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

oracle12c数据库安装(静默安装

写在前面 本教程是在Linux下安装oracle12c数据库&#xff0c;由于在有些情况下并没有图形化安装界面&#xff0c;所以这里介绍在linux下通用的安装方式&#xff1a;静默安装&#xff0c;通俗的说就是在linux的命令行窗口安装。 关闭防火墙 systemctl disable firewalld sy…

LeetCode 300. 最长递增子序列

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 300. 最长递增子序列&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCo…

Spring之AOP简单讲解

目录 一&#xff1a;基本概念 二&#xff1a;案例&#xff1a;模拟AOP的基础代码 三&#xff1a;AOP相关概念 四&#xff1a;AOP入门案例思路分析 五&#xff1a;AOP工作流程 六&#xff1a;AOP核心概念 七&#xff1a;AOP切入点表达式 八&#xff1a;xml方式AOP快速入门…

动态内存分配/管理

目录 1、为什么要有动态内存分配 2、动态内存函数介绍 1、malloc 2、free 3、calloc ​编辑 4、realloc 3、动态内存常见的错误 4、动态内存开辟相关好题 5、c/c程序内存开辟示意图 int a, int arr[10] 是固定地向内存申请连续的一块空间&#xff0c;但不能变长或变…

快速理解机器学习、深度学习与自然语言处理

这篇文章对机器学习、深度学习、自然语言处理进行了简单的介绍&#xff0c;适合快速学习NLP与AI、ML和DL的关系。 机器学习、深度学习、自然语言处理的关系 机器学习、深度学习、自然语言处理的关系如图所示 1 机器学习&#xff08;Machine Learning&#xff09; 机器学习…

python中的设计模式:单例模式、工厂模式

目录 一.设计模式 二.单例模式 二.工厂模式 优点: 总结 一.设计模式 设计模式是一种编程套路&#xff0c;可以极大的方便程序的开发。 最常见、最经典的设计模式,就是我们所学习的面向对象了。 除了面向对象外,在编程中也有很多既定的套路可以方便开发&#xff0c;我们称…

《Linux Shell脚本攻略》学习笔记-第三章

3.1 简介 Unix为所有的设备和系统功能提供了文件形式的借口。可以通过这些特殊文件直接访问设备以及系统功能。 3.2 生成任意大小的文件 创建特定大小的文件最简单的方法就是利用dd命令。dd命令会克隆给定的输出内容&#xff0c;然后将一模一样的一份副本写入输出。 if表示输入…

个人总结:Mysql知识图谱

使用思维导图工具对mysql使用&#xff0c;进行知识总结。着重说下explanid SELECT识别符。这是SELECT查询序列号。这个不重要,查询序号即为sql语句执行的顺序select_type select类型table 输出的行所用的表&#xff0c;这个参数显而易见&#xff0c;容易理解partitions type 连…

Android系统启动(四) — Launcher 启动过程

1 Launcher 概述 系统启动的最后一步是启动一个应用程序来显示系统中已经安装的应用程序&#xff0c;这个应用程序就叫做 Launcher。Launcher 在启动过程中会请求 PackageManagerService 返回系统中已经安装的应用程序信息&#xff0c;并将这些信息封装成一个快捷图标列表显示…

阿里系-淘宝接口抓取及相关问题

阿里系-淘宝接口抓取 一、安装charlse抓包工具 官方下载地址 安装证书 二、安装xposed hook框架 Xponsed简介 具体安装步骤 三、安装模块 关闭阿里系ssl验证 开启http模式 支持支付宝、淘宝、淘宝直播各个接口抓取 四、效果如下 接下去一段时间更新阿里系相关接口 文章目录 一、…

搞技术的要不要学习财务知识

越是大型的集团或者企业&#xff0c;公司里面设立的部门就越多&#xff0c;也越细化&#xff0c;各部门之间既相互独立管理&#xff0c;又是相互的辅助支持&#xff0c;所以在工作中经常遇到这样的一个问题&#xff0c;就是做技术的要不要学习财务知识。这个问题其实就是把技术…

折半查找算法[二分查找法]算法的实现和解决整数溢出问题~

算法实现的要求&#xff1a; 折半查找法又称为二分查找法&#xff0c;这种方法对待查找的列表有两个要求&#xff1a; 1&#xff1a;必须采用顺序存储结构 2&#xff1a;必须按关键字大小有序排列算法思想&#xff1a; 将表中间位置记录的关键字与查找关键字进行比较&#x…

synchronized实现原理

0. 前言 造成线程安全问题的主要诱因有两点&#xff0c;一是存在共享数据(也称临界资源)&#xff0c;二是存在多个线程共同操作共享数据。因此为了解决线程安全问题&#xff0c;我们可能需要这样一个方案&#xff0c;当存在多个线程操作共享数据时&#xff0c;需要保证同一时刻…

ICV:2022年稀释制冷机全球市场规模达2.11亿美元,2028年有望出现突破点

全球前沿科技咨询机构ICV于2023年初发布了稀释制冷机&#xff08;DR&#xff09;的市场分析报告&#xff0c;ICV在报告中表示&#xff0c;2019-2015稀释制冷机的年均增长率达到8.59%以上&#xff0c;且增长率逐年上升。2022年全球稀释制冷机市场规模将达到2.11亿美元&#xff0…

从Deepmind最新成果DreamerV3启发的通用AI技术分析

一、背景 本文系个人观点&#xff1a;错漏在所难免&#xff0c;仅供参考 北京时间 1 月 12 日&#xff0c;DeepMind 官方推特发文&#xff0c;正式官宣 DreamerV3&#xff0c;这是首个能在游戏「我的世界」(Minecraft) 中不参考人类数据&#xff0c;就能从头开始收集钻石的通…

1.16中断实验

一.异常处理流程 1.异常处理流程 &#xff08;1&#xff09;保存现场&#xff08;CPU自动完成&#xff09; 将CPSR中状态保存到SPSR_<MODE>中 将CPSR寄存器的状态位T&#xff0c;改为ARM状态 根据需要&#xff0c;进制IRQ,FIQ中断&#xff0c;修改C…

java springboot 项目构建报错解决办法

这里总结了一些常见的springboot 项目构建报错及解决办法 错误: 无效的源发行版:17 错误原因 build.gradle 文件中可以通过下面两项来指定项目运行所需的jdk版本 sourceCompatibility:指定编译编译.java文件的jdk版本 targetCompatibility&#xff1a;确保class文件与target…

ARM 看门狗定时器

一、什么是看门狗、有什么用 (1) 看门狗定时器和普通的定时器并无本质区别。定时器可以设定一个时间&#xff0c;在这个时间完成之前定时器不断计时&#xff0c;时间到的时候&#xff0c;定时器会复位 CPU&#xff08;重启系统&#xff09;。 (2 )系统正常工作的时候当然不希望…

feign漫谈

feign的简单使用。 文章目录什么是feign准备工作三.如何使用3.1 定义pom文件3.2 定义配置文件及启动类注解3.3 定义feign接口什么是feign 远程调用框架 准备工作 需要nacos环境&#xff1a; 涉及到feign调用&#xff0c;就没法抛开注册中心&#xff0c;接下来我们使用主流的…

使用Python创建websocket服务端并给出不同客户端的请求

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 使用Python创建websocket服务端&#xff0c;并给出不同客户端的请求 一、 WebSocket是啥 WebSocket 和HTTP一样&#xff0c;也是一种通…