[C#] 走进 LINQ 的世界
走进 LINQ 的世界
序
在此之前曾发表过三篇关于 LINQ 的随笔:
进阶:《LINQ 标准查询操作概述》(强烈推荐)
技巧:《Linq To Objects - 如何操作字符串》 和 《Linq To Objects - 如何操作文件目录》
现在,自己打算再整理一篇关于 LINQ 入门的随笔,也是图文并茂的哦。
目录
LINQ 简介
语言集成查询 (LINQ) 是 Visual Studio 2008 和 .NET Framework 3.5 版中引入的一项创新功能。
传统上,针对数据的查询都是以简单的字符串表示,而没有编译时类型检查或 IntelliSense 支持。此外,您还必须针对以下各种数据源学习一种不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。 通过LINQ, 您可以使用语言关键字和熟悉的运算符针对强类型化对象集合编写查询。
一、介绍 LINQ 查询
查询是一种从数据源检索数据的表达式。随着时间的推移,人们已经为各种数据源开发了不同的语言;例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。因此,开发人员不得不针对他们必须支持的每种数据源或数据格式而学习新的查询语言。LINQ 通过提供一种跨数据源和数据格式使用数据的一致模型,简化了这一情况。在 LINQ 查询中,始终会用到对象。可以使用相同的编码模式来查询和转换 XML 文档、SQL 数据库、ADO.NET 数据集、.NET 集合中的数据以及对其有 LINQ 提供程序可用的任何其他格式的数据。
1.1 查询操作的三个部分
操作三部曲:①取数据源 ②创建查询 ③执行查询
internal class Program
{
private static void Main(string[] args)
{
//1.获取数据源
var nums = new int[] { , , , , , , }; //2.创建查询
var numQuery =
from num in nums
where (num % ) ==
select num; //3.执行查询
foreach (var num in numQuery)
{
Console.WriteLine("{0}", num);
}
}
}
下图显示了完整的查询操作。在 LINQ 中,查询的执行与查询本身截然不同;换句话说,查询本身指的是只创建查询变量,不检索任何数据。
1.2 数据源
在上一个示例中,由于数据源是数组,因此它隐式支持泛型 IEnumerable<T> 接口。支持 IEnumerable<T> 或派生接口(如泛型 IQueryable<T>)的类型称为可查询类型。
//从 XML 中创建数据源
//using System.Xml.Linq;
var contacts = XElement.Load(@"c:\xxx.xml");
在 LINQ to SQL 中,首先需要创建对象关系映射。 针对这些对象编写查询,然后由 LINQ to SQL 在运行时处理与数据库的通信。
var db = new Northwnd(@"c:\northwnd.mdf"); //查询在伦敦的客户
var custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
Customers 表示数据库中的特定表
1.3 查询
查询指定要从数据源中检索的信息。 查询还可以指定在返回这些信息之前如何对其进行排序、分组和结构化。 查询存储在查询变量中,并用查询表达式进行初始化。
1.4 查询执行
1.延迟执行
如前所述,查询变量本身只是存储查询命令。 实际的查询执行会延迟到在 foreach 语句中循环访问查询变量时发生。 此概念称为“延迟执行”。
2.强制立即执行
对一系列源元素执行聚合函数的查询必须首先循环访问这些元素。Count、Max、Average 和 First 就属于此类查询。由于查询本身必须使用 foreach 以便返回结果,因此这些查询在执行时不使用显式 foreach 语句。另外还要注意,这些类型的查询返回单个值,而不是 IEnumerable 集合。
var numbers = new int[] { , , , , , , }; var evenNumQuery =
from num in numbers
where (num % ) ==
select num; var evenNumCount = evenNumQuery.Count();
若要强制立即执行任意查询并缓存其结果,可以调用 ToList<TSource> 或 ToArray<TSource> 方法。
var numQuery2 =
(from num in numbers
where (num % ) ==
select num).ToList(); var numQuery3 =
(from num in numbers
where (num % ) ==
select num).ToArray();
此外,还可以通过在紧跟查询表达式之后的位置放置一个 foreach 循环来强制执行查询。但是,通过调用 ToList 或 ToArray,也可以将所有数据缓存在单个集合对象中。
二、基本 LINQ 查询操作
2.1 获取数据源:from
在 LINQ 查询中,第一步是指定数据源。像在大多数编程语言中一样,必须先声明变量,才能使用它。在 LINQ 查询中,最先使用 from 子句的目的是引入数据源和范围变量。
//queryAllCustomers 是 IEnumerable<Cutsomer> 类型
//数据源 (customers) 和范围变量 (cust)
var queryAllCustomers = from cust in customers
select cust;
范围变量类似于 foreach 循环中的迭代变量,但在查询表达式中,实际上不发生迭代。执行查询时,范围变量将用作对 customers 中的每个后续元素的引用。因为编译器可以推断 cust 的类型,所以您不必显式指定此类型。
2.2 筛选:where
也许最常用的查询操作是应用布尔表达式形式的筛选器。此筛选器使查询只返回那些表达式结果为 true 的元素。使用 where 子句生成结果。实际上,筛选器指定从源序列中排除哪些元素。
var queryLondonCustomers = from cust in customers
where cust.City = "London"
select cust;
只返回地址位于伦敦的 customers。
您可以使用熟悉的 C# 逻辑 AND(&&)和 OR(||) 运算符来根据需要在 where 子句中应用任意数量的筛选表达式。
where cust.City = "London" && cust.Name = "Devon"
若要只返回位于“伦敦”和姓名为“Devon”的客户
where cust.City = "London" || cust.Name = "Paris"
若要返回位于伦敦或巴黎的客户
2.3 排序:orderby
通常可以很方便地将返回的数据进行排序。orderby 子句将使返回的序列中的元素按照被排序的类型的默认比较器进行排序。
var queryLondonCustomers = from cust in customers
where cust.City = "London"
orderby cust.Name descending
select cust;
按 Name 属性对结果进行排序
因为 Name 是一个字符串,所以默认比较器执行从 A 到 Z 的字母排序。若要按相反顺序(从 Z 到 A)对结果进行排序,请使用 orderby…descending 子句。
2.4 分组:group
使用 group 子句,您可以按指定的键分组结果。
var queryLondonCustomers = from cust in customers
group cust by cust.City; foreach (var queryLondonCustomer in queryLondonCustomers)
{
Console.WriteLine(queryLondonCustomer.Key);
foreach (var cust in queryLondonCustomer)
{
Console.WriteLine(cust.Name);
}
}
您可以指定结果应按 City 分组,以便位于伦敦或巴黎的所有客户位于各自组中。
在本例中,cust.City 是键。
在使用 group 子句结束查询时,结果采用列表的列表形式。列表中的每个元素是一个具有 Key 成员及根据该键分组的元素列表的对象。在循环访问生成组序列的查询时,您必须使用嵌套的 foreach 循环。外部循环用于循环访问每个组,内部循环用于循环访问每个组的成员。
如果您必须引用组操作的结果,可以使用 into 关键字来创建可进一步查询的标识符。
//custQuery 是 IEnumable<IGrouping<string, Customer>> 类型
var custQuery = from cust in customers
group cust by cust.City
into custGroup
where custGroup.Count() >
orderby custGroup.Key
select custGroup;
这里的查询只返回那些包含两个以上的客户的组。
2.5 联接:join
联接运算创建数据源中没有显式建模的序列之间的关联。例如,您可以执行联接来查找位于同一地点的所有客户和经销商。在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。
var innerJoinQuery = from cust in customers
join dist in distributors on cust.City equals dist.City
select new {CustomerName = cust.Name, DistributorName = dist.Name};
例如,您可以执行联接来查找位于同一地点的所有客户和经销商。
在 LINQ 中,join 子句始终针对对象集合而非直接针对数据库表运行。
在 LINQ 中,您不必像在 SQL 中那样频繁使用 join,因为 LINQ 中的外键在对象模型中表示为包含项集合的属性。
from order in Customer.Orders...
例如,Customer 对象包含 Order 对象的集合。不必执行联接,只需使用点表示法访问订单。
2.6 选择(投影):select
select 子句生成查询结果并指定每个返回的元素的“形状”或类型。
例如,您可以指定结果包含的是整个 Customer 对象、仅一个成员、成员的子集,还是某个基于计算或新对象创建的完全不同的结果类型。当 select 子句生成除源元素副本以外的内容时,该操作称为“投影”。
三、使用 LINQ 进行数据转换
语言集成查询 (LINQ) 不仅可用于检索数据,而且还是一个功能强大的数据转换工具。通过使用 LINQ 查询,您可以将源序列用作输入,并采用多种方式修改它以创建新的输出序列。您可以通过排序和分组来修改该序列,而不必修改元素本身。但是,LINQ 查询的最强大的功能是能够创建新类型。这一功能在 select 子句中实现。 例如,可以执行下列任务:
3.1 将多个输入联接到一个输出序列
class Student
{
public string Name { get; set; } public int Age { get; set; } public string City { get; set; } public List<int> Scores { get; set; }
} class Teacher
{
public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string City { get; set; } }
学生和老师两个类
internal class Program
{
private static void Main(string[] args)
{
//创建第一个数据源
var students = new List<Student>()
{
new Student()
{
Age = ,
City = "广州",
Name = "小C",
Scores = new List<int>(){,,,}
},
new Student()
{
Age = ,
City = "广西",
Name = "小明",
Scores = new List<int>(){,,,}
},
new Student()
{
Age = ,
City = "梦里",
Name = "小叁",
Scores = new List<int>(){,,,}
}
}; //创建第二个数据源
var teachers = new List<Teacher>()
{
new Teacher()
{
Age = ,
City = "梦里",
Name = "啵哆"
},
new Teacher()
{
Age = ,
City = "云南",
Name = "小红"
},
new Teacher()
{
Age = ,
City = "河南",
Name = "丽丽"
}
}; //创建查询
var peopleInDreams = (from student in students
where student.City == "梦里"
select student.Name)
.Concat(from teacher in teachers
where teacher.City == "梦里"
select teacher.Name); //执行查询
foreach (var person in peopleInDreams)
{
Console.WriteLine(person);
} Console.Read();
}
}
控制台输出代码。
3.2 选择各个源元素的子集
1. 若要只选择源元素的一个成员,请使用点运算。
var query = from cust in Customers
select cust.City;
2. 若要创建包含源元素的多个属性的元素,可以使用具有命名对象或匿名类型的对象初始值设定项。
var query = from cust in Customer
select new {Name = cust.Name, City = cust.City};
3.3 将内存中的对象转换为 XML
//创建数据源
var students = new List<Student>()
{
new Student()
{
Age = ,
Name = "小A",
Scores = new List<int>() {,,, }
},
new Student()
{
Age = ,
Name = "小B",
Scores = new List<int>() {,,, }
},
new Student()
{
Age = ,
Name = "小啥",
Scores = new List<int>() {,,, }
}
}; //创建查询
var studentsToXml = new XElement("Root",
from student in students
let x = $"{student.Scores[0]},{student.Scores[1]},{student.Scores[2]},{student.Scores[3]}"
select new XElement("student",
new XElement("Name", student.Name),
new XElement("Age", student.Age),
new XElement("Scores", x))
); //执行查询
Console.WriteLine(studentsToXml);
3.4 对源元素执行操作
输出序列可能不包含源序列的任何元素或元素属性。输出可能是通过将源元素用作输入参数计算出的值的序列。
//数据源
double[] radii = {, , }; //创建查询
var query = from radius in radii
select $"{radius * radius * 3.14}"; //执行查询
foreach (var i in query)
{
Console.WriteLine(i);
}
【备注】$"{radius * radius * 3.14}" 相当于 string.Format("{0}",radius * radius * 3.14),这里采用的是 C# 6.0 的语法。
四、LINQ 查询操作的类型关系
LINQ 查询操作在数据源、查询本身及查询执行中是强类型的。查询中变量的类型必须与数据源中元素的类型和 foreach 语句中迭代变量的类型兼容。强类型可以保证在编译时捕获类型错误,以便及时改正。
4.1 不转换源数据的查询
下图演示不对数据执行转换的 LINQ to Objects 查询操作。源包含一个字符串序列,查询输出也是一个字符串序列。
①数据源的类型参数决定范围变量的类型。
②选择的对象的类型决定查询变量的类型。此处的 name 为一个字符串。因此,查询变量是一个 IEnumerable<字符串>。
③在 foreach 语句中循环访问查询变量。因为查询变量是一个字符串序列,所以迭代变量也是一个字符串。
4.2 转换源数据的查询
下图演示对数据执行简单转换的 LINQ to SQL 查询操作。查询将一个 Customer 对象序列用作输入,并只选择结果中的 Name 属性。因为 Name 是一个字符串,所以查询生成一个字符串序列作为输出。
①数据源的类型参数决定范围变量的类型。
②select 语句返回 Name 属性,而非完整的 Customer 对象。因为 Name 是一个字符串,所以 custNameQuery 的类型参数是 string,而非Customer。
③因为 custNameQuery 是一个字符串序列,所以 foreach 循环的迭代变量也必须是 string。
下图演示另一种转换。select 语句返回只捕获原始 Customer 对象的两个成员的匿名类型。
①数据源的类型参数始终为查询中的范围变量的类型。
②因为 select 语句生成匿名类型,所以必须使用 var 隐式类型化查询变量。
③因为查询变量的类型是隐式的,所以 foreach 循环中的迭代变量也必须是隐式的。
4.3 让编译器推断类型信息
您也可以使用关键字 var,可用于查询操作中的任何局部变量。但是,编译器为查询操作中的各个变量提供强类型。
五、LINQ 中的查询语法和方法语法
我们编写的 LINQ 查询语法,在编译代码时,CLR 会将查询语法转换为方法语法。这些方法调用标准查询运算符的名称类似 Where、Select、GroupBy、Join、Max和 Average,我们也是可以直接使用这些方法语法的。
查询语法和方法语法语义相同,但是,许多人员发现查询语法更简单、更易于阅读。某些查询必须表示为方法调用。例如,必须使用方法调用表示检索元素的数量与指定的条件的查询。还必须使用方法需要检索元素的最大值在源序列的查询。System.Linq 命名空间中的标准查询运算符的参考文档通常使用方法语法。
5.1 标准查询运算符扩展方法
static void Main(string[] args)
{
var nums = new int[] { , , , }; //创建查询表达式
var qureyNums = from n in nums
where n % ==
orderby n descending
select n; Console.WriteLine("qureyNums:");
foreach (var n in qureyNums)
{
Console.WriteLine(n);
} //使用方法进行查询
var queryNums2 = nums.Where(n => n % == ).OrderByDescending(n => n); Console.WriteLine("qureyNums2:");
foreach (var n in queryNums2)
{
Console.WriteLine(n);
} Console.Read();
}
下面的示例演示简单的查询表达式和编写为基于方法的查询的语义上等效的查询。
两个示例的输出是相同的。您可以看到两种形式的查询变量的类型是相同的:IEnumerable<T>。
若要了解基于方法的查询,让我们进一步地分析它。注意,在表达式的右侧,where 子句现在表示为对 numbers 对象的实例方法,在您重新调用该对象时其类型为 IEnumerable<int>。如果您熟悉泛型 IEnumerable<T> 接口,那么您就会了解,它不具有 Where 方法。但是,如果您在 Visual Studio IDE 中调用 IntelliSense 完成列表,那么您不仅将看到 Where 方法,而且还会看到许多其他方法,如 Select、SelectMany、Join 和Orderby。下面是所有标准查询运算符。
尽管看起来 IEnumerable<T> 似乎已被重新定义以包括这些附加方法,但事实上并非如此。这些标准查询运算符都是作为“扩展方法”实现的。
5.2 Lambda 表达式
在前面的示例中,通知该条件表达式 (num % 2 == 0) 是作为内联参数。Where 方法:Where(num => num % 2 == 0) 此内联表达式称为 lambda 表达式。将代码编写为匿名方法或泛型委托或表达式树是一种便捷的方法,否则编写起来就要麻烦得多。=> 是 lambda 运算符,可读为“goes to”。运算符左侧的 num 是输入变量,与查询表达式中的 num 相对应。编译器可推断 num 的类型,因为它了解 numbers 是泛型 IEnumerable<T> 类型。lambda 表达式与查询语法中的表达式或任何其他 C# 表达式或语句中的表达式相同;它可以包括方法调用和其他复杂逻辑。“返回值”就是表达式结果。
5.3 查询的组合性
在上面的代码示例中,请注意 OrderBy 方法是通过在对 Where 的调用中使用点运算符来调用的。Where 生成筛选序列,然后 Orderby 通过对该序列排序来对它进行操作。因为查询会返回 IEnumerable,所以您可通过将方法调用链接在一起,在方法语法中将这些查询组合起来。这就是在您通过使用查询语法编写查询时编译器在后台所执行的操作。并且由于查询变量不存储查询的结果,因此您可以随时修改它或将它用作新查询的基础,即使在执行它后。
传送门
入门:《走进 LINQ 的世界》
进阶:《LINQ 标准查询操作概述》(强烈推荐)
技巧:《Linq To Objects - 如何操作字符串》 和 《Linq To Objects - 如何操作文件目录》
本文首联:http://www.cnblogs.com/liqingwen/p/5832322.html
【参考】https://msdn.microsoft.com/zh-cn/library/bb397897(v=vs.100).aspx 等
【来源】本文引用部分微软官方文档的图片
[C#] 走进 LINQ 的世界的更多相关文章
- C#7.2——编写安全高效的C#代码 c# 中模拟一个模式匹配及匹配值抽取 走进 LINQ 的世界 移除Excel工作表密码保护小工具含C#源代码 腾讯QQ会员中心g_tk32算法【C#版】
C#7.2——编写安全高效的C#代码 2018-11-07 18:59 by 沉睡的木木夕, 123 阅读, 0 评论, 收藏, 编辑 原文地址:https://docs.microsoft.com/ ...
- [转]走进 LINQ 的世界
序 在此之前曾发表过三篇关于 LINQ 的随笔: 进阶:<LINQ 标准查询操作概述>(强烈推荐) 技巧:<Linq To Objects - 如何操作字符串> 和 <L ...
- C# LINQ学习笔记一:走进LINQ的世界
本笔记摘抄自:https://www.cnblogs.com/liqingwen/p/5832322.html,记录一下学习过程以备后续查用. LINQ 简介: 语言集成查询(LINQ)是Vi ...
- 走进缓存的世界(三) - Memcache
系列文章 走进缓存的世界(一) - 开篇 走进缓存的世界(二) - 缓存设计 走进缓存的世界(三) - Memcache 简介 Memcache是一个高性能的分布式内存对象缓存系统,用于动态Web应用 ...
- 小丁带你走进git的世界三-撤销修改
一.撤销指令 git checkout还原工作区的功能 git reset 还原暂存区的功能 git clean 还没有被添加进暂存区的文件也就是git还没有跟踪的文件可以使用这个命令清除他们 g ...
- 小丁带你走进git的世界二-工作区暂存区分支
小丁带你走进git的世界二-工作区暂存区分支 一.Git基本工作流程 1.初始化一个仓库 git init git clone git仓库分为两种情况: 第一种是在现有项目或目录下导入所有文件到 ...
- 带你走进rsync的世界
导读 Rsync(remote synchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件,也可以使用 Rsync 同步本地硬盘中的不同目录.rsync共有3种使用方 ...
- 小丁带你走进git的世界三-撤销修改(转)
一.撤销指令 git checkout还原工作区的功能 git reset 还原暂存区的功能 git clean 还没有被添加进暂存区的文件也就是git还没有跟踪的文件可以使用这个命令清除他们 g ...
- 【SpringMVC学习02】走进SpringMVC的世界
上一篇博文主要介绍了springmvc的整个架构执行流程,对springmvc有了宏观上的把握,这一篇博文主要以案例作为驱动,带你走进springmvc的世界.案例中的需求很简单:对商品列表的查询.表 ...
随机推荐
- C语言 · 字符转对比
问题描述 给定两个仅由大写字母或小写字母组成的字符串(长度介于1到10之间),它们之间的关系是以下4中情况之一: 1:两个字符串长度不等.比如 Beijing 和 Hebei 2:两个字符串不仅长度相 ...
- 菜鸟学Struts2——零配置(Convention )
又是周末,继续Struts2的学习,之前学习了,Struts的原理,Actions以及Results,今天对对Struts的Convention Plugin进行学习,如下图: Struts Conv ...
- Modify Branding of FreeCAD
Modify Branding of FreeCAD eryar@163.com This article describes the Branding of FreeCAD. Branding me ...
- ASP.NET Core CORS 简单使用
CORS 全称"跨域资源共享"(Cross-origin resource sharing). 跨域就是不同域之间进行数据访问,比如 a.sample.com 访问 b.sampl ...
- jsp页面无法识别el表达式的解决方案
今天在写一个springmvc的小demo时,碰到一个问题,在jsp页面中书写为${user.username}的表达式语言,在浏览器页面中仍然显示为${user.username},说明jsp根本不 ...
- PHP获取客户端IP
/** * 获取客户端IP */ function getClientIp() { $ip = 'unknown'; $unknown = 'unknown'; if (isset($_SERVER[ ...
- 在Visual Studio Code中配置GO开发环境
一.GO语言安装 详情查看:GO语言下载.安装.配置 二.GoLang插件介绍 对于Visual Studio Code开发工具,有一款优秀的GoLang插件,它的主页为:https://github ...
- document.compatMode
在我电脑屏幕上显示的 电脑是 1920*1080这是在document.compatMode:css1Compat模式 window.screen.availWidth 1920 window.scr ...
- BPM配置故事之案例3-参与者与数据自动加载
这才过了两天,阿海又来了. 阿海:公司决定改进管理方式,以后物资申请的申请人和申请部门要写具体使用人的名字和部门了. 小明:不是要让我改回去吧? 阿海:那太麻烦了,你能不能把申请人改成选择,选好人自动 ...
- DockerCon 2016 – 微软带来了什么?
根据Forrester的调查,接近半数的企业CIO在考虑IT架构的时候更乐于接受开源方案,这主要是基于低成本,避免供应商锁定和敏捷的需求:同时另外一家North Bridge的调研机构的调查显示,20 ...