首页 > 其他分享 >第8章 LINQ 查询

第8章 LINQ 查询

时间:2024-12-29 23:42:48浏览次数:1  
标签:Name IEnumerable LINQ Replace var 查询 Where

第8章 LINQ 查询

8.2 流式语法

8.2.2 使用 Lambda 表达式

常用运算符

Where()

筛选器

Order()

排序器

Select()

映射器

Take()

获取前 x 个元素

Skip()

跳过前 x 个元素

Reverse()

反转所有元素

First()

获取第一个元素

Last()

获取最后一个元素

ElementAt()

获取第 x 个元素

Count()

获取元素数量

Min()

获取元素最小值

Contains()

查询是否包含某元素

Any()

查询是否包含指定条件的元素

Concat()

首尾拼接两个集合

Union()

求两个集合的并集(去重)

8.2.2.2 Lambda 表达式和元素类型

标准的查询运算符使用了以下的类型参数名称:

方向类型名称 含义
TSource 输入序列的元素类型
TResult 输出序列的元素类型(如果和 TSource ​不一致的话)
TKey 在排序、分组、连接操作中作为键的元素类型

8.3 查询表达式

查询表达式一般以 from ​子句开始,最后以 select ​或者 group ​子句结束。

编译器在处理查询表达式前会将其翻译为流式语法形式,这意味着任何可以用查询语法完成的逻辑也可以用流式语法编写,以下两段代码等价:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

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

query.Dump();
var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" }.AsQueryable();

IEnumerable<string> query =
    names.Where (n => n.Contains("a"))
    .Order      (n => n.Length)
    .Select     (n => n.ToUpper());

query.Dump();

Eureka

若删除了 using System.Linq​ 指令,查询表达式也将无法编译。因为编译器在编译 Where、OrderBy 和 Select 这些运算符时仍会绑定到与之对应的流式方法。

8.3.1 范围变量

在之前的例子中,每一个查询子句都使用了范围变量 n,并且不同子句的范围变量枚举的序列都是不同的:

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

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

query.Dump();
var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" }.AsQueryable();

IEnumerable<string> query =
    names.Where (n => n.Contains("a"))
    .Order      (n => n.Length)
    .Select     (n => n.ToUpper());

query.Dump();

正如流式语法那样,每个 n 只在当前范围内生效。以下的子句也可以在查询表达式中引入新的范围变量:

  • let
  • into
  • 新的 from​ 子句
  • join

8.3.3 LINQ 查询语法与 SQL 语法的不同

C7.0 核心技术指南 第7版.pdf - p400 - C7.0 核心技术指南 第 7 版-P400-20240318224935

8.3.3 查询语法和流式语法 & 8.3.4 混合查询语法

查询语法仅支持如下:

Where​、Select​、SelectMany​、OrderBy​、ThenBy​、OrderByDescending​、ThenByDescending​、GroupBy​、Join​、GroupJoin

如果没有合适的查询语法,可以混用这两种语法:

(from n in names where n.Contains ("a") select n).Count()
	.Dump ("Names containing the letter 'a'");

string first = (from n in names orderby n select n).First()
	.Dump ("First name, alphabetically");

C7.0 核心技术指南 第7版.pdf - p401 - C7.0 核心技术指南 第 7 版-P401-20240318225533

8.4 延迟执行

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

  • 返回 单个元素标量值 的运算符,例如:First​ ​或 Count​​
  • 转换 运算符:ToArray​、ToList​、ToDictionary​、ToLookup

上述运算符都会立即执行,因为其结果类型不具备任何延迟执行的机制(延迟执行需要结果是 可枚举 类型)。例如,Count​ 方法返回一个整数,而整数是无法再枚举的

8.4.1 重复执行

延迟执行的另一个后果是:当重复枚举时,延迟执行的查询也会重复执行。此时可以使用 转换 运算符,例如 ToArray ​或 ToList ​来避免重复执行。

8.4.2 捕获变量

如果 Lambda 捕获了外部变量,那么该变量的值将在表达式 执行 时决定。如下代码输出的值为“ 20 40 ”:

int[] numbers = { 1, 2 };
int factor = 10;
IEnumerable<int> query = numbers.Select (n => n * factor);
factor = 20;
query.Dump ();

尤其要注意 for 循环中对 索引 的误用:

for (int i = 0; i < vowels.Length; i++)
	query = query.Where (c => c != vowels[i]);

C7.0 核心技术指南 第7版.pdf - p404 - C7.0 核心技术指南 第 7 版-P404-20240330165546-rwr1zcr

Warn

上述现象不仅限于 LINQ 和 Lambda 表达式,任何闭包都会有此问题!例如:

int value = 10;
void DoSomething()
{
    value.Dump();
}
value = 20;
// 输出 20
DoSomething();

8.6 构造方式

8.6.2 into​ 关键字

在查询表达式中我们提到:

查询表达式一般以 from ​子句开始,最后以 select ​或者 group ​子句结束。

结束子句运行后将无法进行其他查询,此时我们可以使用 into​ 关键字:

var query =
	from   n in names
	select n.Replace ("a", "").Replace ("e", "").Replace ("i", "").Replace ("o", "").Replace ("u", "")
	into   noVowel 
	where  noVowel.Length > 2
    orderby noVowel
    select noVowel;

into 关键字仅能出现在 select ​ 和 group ​ 子句之后

C7.0 核心技术指南 第7版.pdf - p413 - C7.0 核心技术指南 第 7 版-P413-20240330172956-yhz9dnh

into​ 的作用域

into 关键字后面的查询语句 不能 使用之前定义的范围变量,因此以下的查询是 无法 通过编译的:

var query =
	from   n1 in names
	select n1.ToUpper()
	into   n2                          // 后续语句仅有 n2 有效.
	where  n1.Contains ("x")           // 非法: n1 已不在作用域.
	select n2;

它相当于如下链式表达式:

var query = names
	.Select (n1 => n1.ToUpper())
	.Where (n2 => n1.Contains ("x"));     // 错误: n1 已不在作用域

8.7 映射方式

8.7.1 对象初始化器 & 8.7.2 匿名类型

select 子句不但可以将结果映射为标量元素,也可以映射为类型实例,这个过程可以通过 对象初始化 器完成:

// 数据
var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" }.AsQueryable();
IEnumerable<TempProjectionItem> temp =
    from n in names
    select new TempProjectionItem
    {
        Original = n,
        Vowelless = n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "").Replace("u", "")
    };

// 临时类型
class TempProjectionItem
{
    public string Original;      // Original name
    public string Vowelless;     // Vowel-stripped name
}

我们也可以使用匿名类型,省去类型定义:

var intermediate = from n in names
select new
{
	Original = n,
	Vowelless = n.Replace ("a", "").Replace ("e", "").Replace ("i", "").Replace ("o", "").Replace ("u", "")
};

8.7.3 let 关键字

在8.6.2 into 关键字中我们提到:

into 关键字后面的查询语句 不能 使用之前定义的范围变量,因此以下的查询是 无法 通过编译的:

let 关键字解决了这一问题:

var value = from n in names
	select new
	{
		Original = n,
		Vowelless = n.Replace ("a", "").Replace ("e", "").Replace ("i", "").Replace ("o", "").Replace ("u", "")
	}
	into   temp
	where  temp.Vowelless.Length > 2
	select temp.Original;
var value = from n in names
	let vowelless = n.Replace ("a", "").Replace ("e", "").Replace ("i", "").Replace ("o", "").Replace ("u", "")
	where vowelless.Length > 2
	orderby vowelless
	select n

let 有两个方面的功能:

  1. 同时映射了 的元素和 已有 的元素。
  2. 允许在一个查询中无须重写而复用其中的表达式。

8.8 解释型查询

LINQ 提供了两种平行的架构:

本地查询 解释型查询
适用范围 本地对象集合 远程数据源
适用类型 IEnumerable<T>​ 集合 IQuerable<T>​ 接口
工作方式 使用链式表达式,编译为 IL 代码 运行时解释,生成表达式树

IQuerable<T>​ 的实现方式有两种:

  1. LINQ to SQL
  2. Entity Framework(EF)

C7.0 核心技术指南 第7版.pdf - p418 - C7.0 核心技术指南 第 7 版-P418-20240405163532-1uikzy4

8.8.1 解释型查询的工作机制

以如下代码为例:

IQueryable<string> query = from c in Customers
    where   c.Name.Contains ("a")
    orderby c.Name.Length
    select  c.Name.ToUpper();
1. 将查询语法转换为流式语法

这与本地查询相同,上述代码将转化为:

IQueryable<string> query = customers.Where   (n => n.Name.Contains("a"))
                                    .OrderBy (n => n.Name.Length)
                                    .Select  (n => n.Name.ToUpper());
2. 解析运算符方法

customer​(Table<T>​ 类型)实现了 IQueryable<T>​ 接口,因此上述方法将被解析为 IQueryable<T>​ 中的方法:

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

可以看到,Lambda 转换为 Expression​,而非 Func​ 委托。Expression​ 将进一步翻译为表达式树。

Tips

IQueryable<T>​ 接口继承了 IEnumerable<T>​ 接口,不过 LINQ 对它们分别编写了不同的扩展方法。对比 IEnumerable<T>​ 中的 Where​:

public static IEnumerable<TSource> Where<TSource> (this
    IEnumerable<TSource> source, Func<TSource, bool> predicate)
3. 执行

解释型查询会遵循延迟执行模式,表达式树在执行时会生成 SQL 语句。与本地查询不同。进行解释型查询时,会先遍历整个表达式树,并将其作为一个整体(一条 SQL 语句)来处理:

SELECT UPPER([to].[Name]) AS [value]
FROM [Customer] AS [to]
WHERE [to].[Name] LIKE @pO
ORDER BY LEN([to].[Name])

C7.0 核心技术指南 第7版.pdf - p420 - C7.0 核心技术指南 第 7 版-P420-20240405190210-48i40y3

8.8.2 混合使用解释型查询和本地查询

以如下代码为例,我们定义一个扩展方法,接受、返回 IEnumerable<string>​ 类型的对象,以此混合使用解释型查询和本地查询:

public static IEnumerable<string> Pair (this IEnumerable<string> source) {
    string firstHalf = null;
    foreach (string element in source) {
        if (firstHalf == null)
            firstHalf = element;
        else {
            yield return firstHalf + ", " + element;
            firstHalf = null;
        }
    }
}
Customers
	.Select (c => c.Name.ToUpper())
	.OrderBy (n => n)
	.Pair()                         // 此处转为本地查询
	.Select ((n, i) => "Pair " + i.ToString() + " = " + n)
	.Dump();
-- 解释型查询将转化为如下 SQL 语句
SELECT UPPER (Name) FROM Customer ORDER BY UPPER (Name)

8.8.3 IEnumerable.AsEnumerable​ 扩展方法

该方法用于将 IQueryable<T>​ 转化为 IEnumerable<T> ​,定义如下:

public static IEnumerable<TSource> AsEnumerable<TSource> (this IEnumerable<TSource> source) {
	return source;
}

如下代码想在 LINQ 中使用正则表达式,而 SQL Server 不支持。我们可以将查询拆分为解释型和本地型两部分:

Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");

// 如下查询将抛出异常
var query = MedicalArticles
	.Where (article => article.Topic == "influenza"
			&& wordCounter.Matches (article.Abstract).Count < 100);
var query = MedicalArticles
	.Where (article => article.Topic == "influenza")
	.AsEnumerable()
	.Where (article => wordCounter.Matches (article.Abstract).Count < 100);
// 等价于
IEnumerable<MedicalArticle> sqlQuery = dataContext.MedicalArticles
	.Where (article => article.Topic == "influenza")
IEnumerable<MedicalArticle> localQuery = sqlQuery
	.Where (article => wordCounter.Matches (article.Abstract).Count < 100);

我们也可以使用 ToArray ​ 或 ToList ​ 达到同样的效果,不过 AsEnumerable​ 可以延迟执行。

8.9 LINQ to SQL 和 Entity Framework

8.9.1 LINQ to SQL(L2S)的实体类 #delay#​ 用不到,等用到了再看

L2S 可以使用任何类表示数据,仅需通过 System.Data.Linq.Mapping​ 命名空间中的特性进行标记。常用的特性和参数有:

  1. [Table]​ 用于

    1. Name​ 标记数据库
  2. [Column]​ 用于 字段属性

    1. IsPrimaryKey​ 标记是否为 主键
    2. Name​ 标记
    3. Storage​ 标记属性的 后备字段名称

例如:

[Table(Name = "Customers")]
public class Customer
{
    [Column(IsPrimaryKey = true, Name = "FullName")]
    public int Id;
  
    private string _name;
  
    [Column(Storage = nameof(_name))]
    public string Name;
}

8.9.2 Entity Framework 的实体类 #delay#​ 当前未接触这部分内容,后面再制卡学习

对于 EF,需要 .edmx 文件,它包含三部分:

  1. 概念模型:描述了 EDM 模型,并对数据库进行了隔离
  2. 存储模型:描述了数据库的大纲(schema)
  3. 映射:定义了概念模型和存储模型的映射关系

通过 .edmx 文件可以创造复杂的映射关系:

  • 将多个表映射为一个实体
  • 将一个表映射为多个实体
  • 使用 ORM 的三种标准方式将表映射为继承类型。

C7.0 核心技术指南 第7版.pdf - p426 - C7.0 核心技术指南 第 7 版-P426-20240405212628-2q9aj2f

以下是实体类的一个例子:

[EdmEntityType (NamespaceName ="NutshellModel", Name = "Customer")]
public partial class Customer{
    [EdmScalarPropertyAttribute (EntityKeyProperty=true, IsNullable=false)]
    public int ID {get;set;}
  
    [EdmScalarProperty (EntityKeyProperty = false, IsNullable = false)]
    public string Name {get; set;}
}

8.9.3 DataContext​ 和 ObjectContext

DataContext​(L2S)和 ObjectContext​(EF)用于查询(仅能用于 SQL Server),使用方式如下:

var l2sContext = new DataContext ("database connection string");
var efContext = new ObjectContext ("entity connection string");

其中,构造器传入数据库连接字符串。对于 ObjectContext​ 还要传入 .edmx 文件的访问方式。

获得上下文对象后,我们可以用它查询、更新数据库内容:

DataContext​ 使用示例
var context = new DataContext ("database connection string")
Table<Customer> customers = context.GetTable<Customer>();
// 查询
Console.WriteLine (customers.Count());
Customer cust = customers.Single (c => c.ID == 2);
// 修改
cust.Name = "Updated Name";
context.SubmitChanges();
ObjectContext​ 使用示例
var context = new ObjectContext ("entity connection string");
context.DefaultContainerName = "NutshellEntities";
ObjectSet<Customer> customers = context.CreateObjectSet<Customer>();
// 查询
Console.WriteLine (customers.Count());
Customer cust = customers.Single (c => c.ID == 2);
// 修改
cust.Name = "Updated Name";
context.SaveChanges();

8.9.3.1 类型化上下文

8.9.3 DataContext 和 ObjectContext 中的示例代码每次查询都需要调用 GetTable​ 或 CreateObjectSet​,并不方便。更好的方式是类型化上下文(typed context):

class Nutshellcontext : DataContext {  // L2S
    public Table<Customer> Customers => GetTable<Customer>();
}
class Nutshellcontext : ObjectContext {  // EF
    public Table<Customer> Customers => CreateObjectSet<Customer>();
}

调用可以得到极大简化:

var context = new Nutshellcontext("connection string");
Console.WriteLine (context.Customers.Count());

8.9.3.2 对象状态跟踪

DataContext​ 和 ObjectContext​ 实例会缓存所有查询结果,重复请求表中同一行时,它总会返回相同的实例。其工作方式如下:

flowchart LR id1(["查询数据"]) --> id2["获取主键"] --> id3{"缓存是否<br/>包含该数据?"} -->|"是"| id4["返回缓存对象"] id3 -->|"否"| id5["缓存该数据"] --> id4

新版 .NET 已不再支持这两个 Context,L2S 已被弃用。转而使用 DbContext​,原理与 ObjectContext​ 相似。

8.9.4 关联 及后续内容

8.9 节这部分内容在新版 .NET 已被弃用,且主要面向 SQL Server,实用性不强,暂时不看

​#delay#​

8.10 构建查询表达式

本节后续例子我们都假定有如下 Product 类:

[Table] public partial class Product
{
    [Column(IsPrimaryKey=true)] public int ID;
    [Column]                    public string Description;
    [Column]                    public bool Discontinued;
    [Column]                    public DateTime LastSale;
}

8.10.1 委托与表达式树

在 2. 解析运算符方法有提到:

  • 本地查询使用 Enumerable​ ​运算符,接受 委托
  • 解释型查询使用 Queryable​ ​运算符,接受 表达式树

以如下代码为例,predicate1​ 和 predicate2不能 互换:

// 委托
Func<Product, bool> predicate1 = p => !p.Discontinued;
// 表达式树
Expression<Func<Product, bool>> predicate2 = p => !p.Discontinued;

IEnumerable<Product> localProducts = ...;
IQueryable<Product> sqlProducts = ...;

var q1 = localProducts.Where(predicate1);
var q2 = sqlProducts.Where(predicate2);

实际上 sqlProducts.Where​ 可以传入 predicate1​,不过这样它实际调用的是 IEnumerable.Where

8.10.1.1 Expression<...>​ 转化为 Funk<...>

Expression<...>​ 的 Compile ​ 方法可以见其转化为 Func<...>​:

var q1 = localProducts.Where(predicate2);  // 无法编译
var q1 = localProducts.Where(predicate2.Compile());

8.10.1.2 AsQueryable

与 8.8.3 IEnumerable.AsEnumerable 扩展方法相似,可以将 IEnumerable​ 实例包装为 IQueryable​ 实例。

该方法主要用于提供兼容性:

IQueryable<Product> FilterSortProducts (IQueryable<Product> input)
{
    return from p in input
           where ...
           order by ...
           select p;
}
void Test()
{
    var dataContext = new Nutshellcontext("connection string");
    Product[] localProducts = dataContext.Products.ToArray();
    var sqlQuery= FilterSortProducts(dataContext.Products);
    var localQuery = FilterSortProducts(localProducts.AsQueryable());
    ...
}

8.10.2 表达式树

讲的太简略了,看不明白 #delay#​

标签:Name,IEnumerable,LINQ,Replace,var,查询,Where
From: https://www.cnblogs.com/hihaojie/p/18639814/chapter-8-linq-query-2p2sug

相关文章

  • 第9章 LINQ 运算符
    第9章LINQ运算符本章所有例子所使用的names数组都是一致的:string[]names={"Tom","Dick","Harry","Marry","Jay"};9.1概述标准查询运算符分三类:输入是序列(IEnumerable​),输出是序列(IEnumerable​)(IEnumerable​→IEnumerable​)输入是序列(IEnumerabl......
  • 第10章 LINQ to XML
    第10章LINQtoXML10.1架构概述——DOM和LINQtoXML的DOMXML文档可以用一棵对象树完整的表示,这称为“文档对象模型(documentobjectmodel)”LINQtoXML由两部分组成:XMLDOM,简称为X-DOM大约10个查询运算符LINQ也可以用于查询W3C标准的旧DOM,不过X-DOM对L......
  • SQL 实战:基于经纬度的距离计算与位置查询
    在位置服务(LBS)系统中,基于地理位置查询和距离计算是核心功能之一。例如:查找附近的商铺、加油站或医院。计算两点之间的实际直线距离。筛选出指定范围内的用户或设备位置。MySQL提供了多种方式实现地理位置查询,包括ST_Distance_Sphere()和经典的Haversine公式。本文将......
  • 什么情况下会放弃索引,直接进行全表扫描查询?
    索引选择性低索引选择性:选择性是指索引列中不同值的数量与总行数的比率。选择性高的索引(即索引列中的值分布较广)通常更有效。全表扫描更高效:如果索引列的选择性很低(即大多数行具有相同或相似的值),全表扫描可能比使用该索引更高效。查询条件不适用索引查询条件复杂:某些查......
  • 【详解】ElasticSearchQuery查询方式
    目录ElasticsearchQuery查询方式1.MatchQuery(匹配查询)2.TermQuery(精确查询)3.RangeQuery(范围查询)4.BoolQuery(布尔查询)5.其他查询方式结论ElasticsearchQuery查询方式Elasticsearch(ES)是一个基于Lucene的高性能、分布式、开源搜索引擎,提供了多种灵活的查询......
  • JAVA连接MYSQL数据库实现查询
    准备驱动(1)查看数据库版本号(2)根据数据库版下载对应版本驱动驱动下载网址:MySQL::DownloadMySQLConnector/J(ArchivedVersions)若没有则选择接近自己版本的低版本。说明:......
  • leetcode1938 查询最大基因差
    给定一棵n个节点的有根树,节点i的父节点为parents[i],根节点的父节点为-1,节点的基因值等于自身编号。有m个询问,queries[i]=[node[i],val[i]],返回从根节点到node[i]的路径上所有节点基因值与val[i]的异或最大值。2<=n<=1E5;1<=m<=3E4;0<=val[i]<=2E5分析:01-trie+离线+dfs。(1)用01......
  • JavaScript引擎在优化标识符查询方面做了什么?
    JavaScript引擎在优化标识符查询方面采取了多种策略和技术,以提高代码执行效率和性能。以下是一些主要的优化方法:作用域链和变量对象的优化:JavaScript引擎通过创建作用域链来管理变量的访问。每个函数都有一个[[Scope]]属性,指向函数的作用域链。当函数执行时,会创建一个执行上下文......
  • 数据库sql语句单表查询
    简单的增删改查操作selectcount(*)fromuserwhereaccount='admin'andpassword='123456'selectcount(*)fromuserwhereaccount="admin"insertintouser(account,password)values("admin","777")updateusersetpa......
  • 百万商品查询,性能提升了10倍
    在现代电商系统中,商品查询是用户最频繁的操作之一。随着商品数量的激增,如何在海量数据中快速准确地查询到用户所需的商品,成为提升用户体验和系统性能的关键。本文将详细介绍一种将百万商品查询性能提升10倍的优化方案。一、问题背景随着电商平台的不断发展,商品种类和数量呈爆......