语言集成查询 (LINQ)
LINQ(Language Integrated Query)是C#中的一种强大的查询语法,
它允许开发者通过类似SQL的方式查询集合、数组和数据库等数据源。
LINQ不仅仅局限于对数据库数据进行操作,还可以对任何实现了IEnumerable<T>或IQueryable<T>接口的数据源进行操作。
LINQ的主要目标是通过简化查询代码,让程序员能够在同一语言环境中操作不同的数据源,无需学习不同的查询语言或编写复杂的代码。
优势:
- Familiar language(熟悉的语言): 开发人员不必为每种类型的数据源或数据格式学习新的查询语言。
- Less coding(更少的代码): 与更传统的方法相比,它减少了要编写的代码量。
- Readable code(代码可读性): LINQ使代码更具可读性,因此其他开发人员可以轻松地理解和维护它。
- Standardized way of querying multiple data sources(查询多个数据源的标准化方法): 相同的LINQ语法可用于查询多个数据源。
- Compile time safety of queries(查询的编译时安全性): 它在编译时提供对象的类型检查。
- IntelliSense Support(智能感知支持): LINQ为通用集合提供了IntelliSense。
- Shaping data(数据形状): 您可以以不同形状检索数据。
LINQ支持两种语法:
- 查询表达式语法(Query Syntax):类似于SQL语法。
- 方法语法(Method Syntax):基于扩展方法的语法。通常与 Lambda 表达式一起使用,表示具体的查询操作。
LINQ的组成部分涵盖了多种数据源的查询操作,主要可以分为以下几大部分(常用):
- LINQ to Objects(内存对象)
- LINQ to Entities(Entity Framework)(数据库框架)
- Parallel LINQ (PLINQ)(异步编程)
- LINQ to XML(XML)
除此之外还有,LINQ to SQL、LINQ to DataSet、LINQ to JSON。
延迟执行
LINQ查询默认是延迟执行的,查询并不会在定义时立即执行,而是在访问查询结果时执行。
也就是说,在我们调用foreach遍历结果或调用ToList()、ToArray()等方法时,查询才会真正执行。
IQueryable 和 IEnumerable
我们可以为实现IEnumerable <T>或 IQueryable <T>接口的类编写LINQ查询。System.Linq的命名空间包括下列类和接口要求对LINQ查询。
IEnumerable
IEnumerable 接口代表一个可以枚举的集合。它适用于对内存中的数据进行查询和操作。查询是在客户端(内存中)执行的,数据在获取时已经加载到了内存中,因此对于IEnumerable来说,所有操作都是立即执行的。
特点:
- 内存中的数据:IEnumerable适用于操作内存中的集合,如数组、列表等。
- 延迟加载:LINQ 查询是延迟执行的,但一旦开始枚举,数据会立即被加载到内存中,之后的操作在内存中完成。
- 延迟执行:虽然LINQ查询是延迟执行的,但在你遍历结果时,查询就会执行,并在内存中进行。
- 只支持前向遍历:IEnumerable 只能从第一个元素开始进行遍历,不能向后遍历。
使用场景:
- 适用于内存中的查询操作,如集合、数组或已经从数据库读取到内存的数据。
优点:
- 简单易用,适合操作内存中的数据。
- 使用时可以通过foreach遍历集合。
缺点:
- 查询效率低:对于大型数据集,所有的数据都必须加载到内存中,导致性能低下,尤其是当查询涉及过滤、排序时。
- 无法利用数据库等外部数据源的功能优化(如索引、缓存等)。
扩展方法:
IQueryable
IQueryable 接口继承自 IEnumerable,并扩展了它的功能,专门用于支持对数据库等远程数据源的查询。
与 IEnumerable 不同的是,IQueryable 的查询是在数据库服务器或远程数据源上执行的,它能够生成SQL查询,并在服务端执行查询,返回查询结果。
特点:
- 服务端查询:IQueryable 主要用于查询数据库等远程数据源,查询操作不会立即执行,而是延迟到实际遍历数据时才在数据库中执行。
- 延迟执行:查询的实际执行是在你枚举查询结果时。
- 优化查询:它允许在服务端执行查询,利用数据库的索引、缓存和其他优化功能,从而提升查询性能。
- 更灵活:IQueryable支持动态构建查询,可以根据条件动态生成查询表达式。
使用场景:
- 适用于对数据库或其他外部数据源的查询操作,尤其是在使用Entity Framework或LINQ to SQL时。
优点:
- 查询优化:在数据库执行查询,可以生成SQL查询并利用数据库的索引、优化等特性。
- 处理大数据集:不需要将所有数据加载到内存中,可以只查询并返回所需的数据,减少内存占用。
缺点:
- 只能用于与数据库等外部数据源的查询,不能用于对内存中的集合进行操作。
相关运算符
标准查询运算符
LINQ中的标准查询运算符实际上是 IEnumerable<T> and IQueryable<T>类型的扩展方法。
它们在System.Linq.Enumerable和System.Linq.Queryable类中定义。
LINQ中提供了50多个标准查询运算符,它们提供了不同的功能,例如过滤,排序,分组,聚合,串联等。
提示
查询语法中的标准查询运算符在编译时转换为扩展方法。所以两者都是一样的。
类别 | 标准查询运算符 |
---|---|
过滤 | Where, OfType |
排序 | OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse |
分组 | GroupBy, ToLookup |
联合 | GroupJoin, Join |
投射 | Select, SelectMany |
聚合 | Aggregate, Average, Count, LongCount, Max, Min, Sum |
修饰 | All, Any, Contains |
元素 | ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault |
集合 | Distinct, Except, Intersect, Union |
分区 | Skip, SkipWhile, Take, TakeWhile |
串联 | Concat |
相等 | SequenceEqual |
范围状态 | DefaultEmpty, Empty, Range, Repeat |
转换 | AsEnumerable, AsQueryable, Cast, ToArray, ToDictionary, ToList |
转换运算符
LINQ中的Conversion运算符可用于转换序列(集合)中元素的类型。
转换运算符分为三种:
- As运算符(AsEnumerable和AsQueryable),
- To运算符(ToArray,ToDictionary,ToList和ToLookup)
- 转换运算符(Cast和OfType)。
方法 | 描述 |
---|---|
AsEnumerable | 将输入序列作为 IEnumerable <T> 返回 |
AsQueryable | 将IEnumerableto转换为IQueryable,以模拟远程查询提供程序 |
Cast | 将非泛型集合转换为泛型集合(IEnumerable到IEnumerable) |
OfType | 基于指定类型筛选集合 |
ToArray | 将集合转换为数组 |
ToDictionary | 根据键选择器函数将元素放入 Dictionary 中 |
ToList | 将集合转换为 List |
ToLookup | 将元素分组到 Lookup<TKey,TElement> |
LINQ to Objects
LINQ to Objects 是 LINQ 的一种实现,专门用于对内存中的集合进行查询。
它允许你对实现了 IEnumerable<T> 接口的集合(如数组、List、Dictionary等)进行过滤、排序、分组和转换等操作。
LINQ to Objects 可以简化对内存数据集合的处理,并提供类似于 SQL 查询的语法来进行数据操作。
特点:
- 面向内存数据:它适用于操作内存中的数据集合,如数组、列表、字典等。
- 延迟执行:大多数 LINQ 查询是延迟执行的,查询不会在定义时立即执行,而是在访问查询结果时才执行(例如,通过 foreach 遍历结果时)。
- 类型安全:LINQ 查询是类型安全的,编译时会进行类型检查,避免了运行时类型错误。
- 强大的查询功能:支持丰富的操作,如过滤、排序、投影、分组、连接、聚合等。
使用场景:
LINQ to Objects 适合处理在内存中的集合或数据结构,通常用于操作简单的集合,如数组、List、Dictionary 等。
常见的 LINQ 方法:
int[] numbers = { 1, 2, 3, 4, 5 };
// 输出: 2, 4
var evenNumbers = numbers.Where(n => n % 2 == 0);
var evenNumbers = from n in numbers
where n % 2 == 0
select n;
// 输出: 1, 4, 9, 16, 25
var squaredNumbers = numbers.Select(n => n * n);
var squaredNumbers = from n in numbers
select n * n;
// 排序
var orderedNumbers = numbers.OrderBy(n => n);
var orderedNumbers = from n in numbers
orderby n
select n;
var orderedNumbers1 = numbers.OrderByDescending(n => n);
var orderedNumbers1 = from n in numbers
orderby n descending
select n;
// 分组
var groupedByEvenOdd = numbers.GroupBy(n => n % 2 == 0);
var groupedByEvenOdd = from n in numbers
group n by n % 2 == 0 into g
select g;
// 聚合(只能用方法)
var sum = numbers.Aggregate((a, b) => a + b);
var sum1 = numbers.Count();
var avg = numbers.Average();
//join
var people = new[] { new { Name = "John", Age = 30 }, new { Name = "Jane", Age = 25 } };
var pets = new[] { new { Owner = "John", PetName = "Rover" } };
var query = people.Join(pets,
person => person.Name,
pet => pet.Owner,
(person, pet) => new { person.Name, pet.PetName });
var query = from person in people
join pet in pets on person.Name equals pet.Owner
select new { person.Name, pet.PetName };
LINQ to Entities
LINQ to Entities 是 LINQ 的一种实现,专门用于在 Entity Framework (EF) 中查询数据库。
它允许你通过 C# 代码以类似 SQL 的方式查询数据库,而不需要编写实际的 SQL 语句。
LINQ to Entities 通过将 LINQ 查询转换为 SQL 查询,并在数据库服务器上执行,从而返回结果到应用程序中。
特点:
- 强类型查询:LINQ to Entities 查询是强类型的,编译时会进行类型检查,确保安全性和准确性。
- 对象映射到数据库:通过 EF,实体类(POCOs)会映射到数据库的表,查询返回的结果也是实体对象。
- 延迟执行:LINQ 查询默认是延迟执行的。即查询在被定义时不会执行,只有在遍历结果或调用操作时,查询才会真正执行并在数据库端运行。
- 自动生成 SQL:LINQ 查询会被 EF 自动翻译为 SQL 查询,并在数据库中执行,返回结果。
注意
- 支持的操作有限:由于 LINQ to Entities 最终会被转换为 SQL 查询,因此只能使用那些能够转换为 SQL 的方法和操作。一些复杂的 C# 代码逻辑可能无法直接翻译为 SQL 语句。
- 查询性能:虽然 LINQ to Entities 可以生成 SQL 查询,但生成的 SQL 并不总是最优的。在复杂查询或大数据量操作时,建议对生成的 SQL 进行分析和优化。
常见的 LINQ 方法:
using (var context = new MyDbContext())
{
// 查询
var customersInSeattle = context.Customers
.Where(c => c.City == "Seattle")
.ToList();
//选择
var customerInfo = context.Customers
.Select(c => new { c.Name, c.City })
.ToList();
//排序
var orderedCustomers = context.Customers
.OrderBy(c => c.Name)
.ToList();
//分组
var customersByCity = context.Customers
.GroupBy(c => c.City)
.ToList();
//聚合
var countSeattleCustomers = context.Customers
.Count(c => c.City == "Seattle");
//连接
var query = context.Customers
.Join(context.Orders,
customer => customer.Id,
order => order.CustomerId,
(customer, order) => new
{
CustomerName = customer.Name,
OrderProduct = order.ProductName
});
//连接并分组
var query = context.Customers
.GroupJoin(context.Orders,
customer => customer.Id,
order => order.CustomerId,
(customer, ordersGroup) => new
{
CustomerName = customer.Name,
Orders = ordersGroup
});
}
Parallel LINQ (PLINQ)
参考并行循环