Skip to content

语言集成查询 (LINQ)

LINQ(Language Integrated Query)是C#中的一种强大的查询语法,

它允许开发者通过类似SQL的方式查询集合、数组和数据库等数据源。

LINQ不仅仅局限于对数据库数据进行操作,还可以对任何实现了IEnumerable<T>或IQueryable<T>接口的数据源进行操作。

LINQ的主要目标是通过简化查询代码,让程序员能够在同一语言环境中操作不同的数据源,无需学习不同的查询语言或编写复杂的代码。

优势:

  1. Familiar language(熟悉的语言): 开发人员不必为每种类型的数据源或数据格式学习新的查询语言。
  2. Less coding(更少的代码): 与更传统的方法相比,它减少了要编写的代码量。
  3. Readable code(代码可读性): LINQ使代码更具可读性,因此其他开发人员可以轻松地理解和维护它。
  4. Standardized way of querying multiple data sources(查询多个数据源的标准化方法): 相同的LINQ语法可用于查询多个数据源。
  5. Compile time safety of queries(查询的编译时安全性): 它在编译时提供对象的类型检查。
  6. IntelliSense Support(智能感知支持): LINQ为通用集合提供了IntelliSense。
  7. Shaping data(数据形状): 您可以以不同形状检索数据。

LINQ支持两种语法:

  1. 查询表达式语法(Query Syntax):类似于SQL语法。
  2. 方法语法(Method Syntax):基于扩展方法的语法。通常与 Lambda 表达式一起使用,表示具体的查询操作。

LINQ的组成部分涵盖了多种数据源的查询操作,主要可以分为以下几大部分(常用):

  1. LINQ to Objects(内存对象)
  2. LINQ to Entities(Entity Framework)(数据库框架)
  3. Parallel LINQ (PLINQ)(异步编程)
  4. 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来说,所有操作都是立即执行的。

特点:

  1. 内存中的数据:IEnumerable适用于操作内存中的集合,如数组、列表等。
  2. 延迟加载:LINQ 查询是延迟执行的,但一旦开始枚举,数据会立即被加载到内存中,之后的操作在内存中完成。
  3. 延迟执行:虽然LINQ查询是延迟执行的,但在你遍历结果时,查询就会执行,并在内存中进行。
  4. 只支持前向遍历:IEnumerable 只能从第一个元素开始进行遍历,不能向后遍历。

使用场景:

  1. 适用于内存中的查询操作,如集合、数组或已经从数据库读取到内存的数据。

优点:

  1. 简单易用,适合操作内存中的数据。
  2. 使用时可以通过foreach遍历集合。

缺点:

  1. 查询效率低:对于大型数据集,所有的数据都必须加载到内存中,导致性能低下,尤其是当查询涉及过滤、排序时。
  2. 无法利用数据库等外部数据源的功能优化(如索引、缓存等)。

扩展方法: An image

IQueryable

IQueryable 接口继承自 IEnumerable,并扩展了它的功能,专门用于支持对数据库等远程数据源的查询。

与 IEnumerable 不同的是,IQueryable 的查询是在数据库服务器或远程数据源上执行的,它能够生成SQL查询,并在服务端执行查询,返回查询结果。

特点:

  1. 服务端查询:IQueryable 主要用于查询数据库等远程数据源,查询操作不会立即执行,而是延迟到实际遍历数据时才在数据库中执行。
  2. 延迟执行:查询的实际执行是在你枚举查询结果时。
  3. 优化查询:它允许在服务端执行查询,利用数据库的索引、缓存和其他优化功能,从而提升查询性能。
  4. 更灵活:IQueryable支持动态构建查询,可以根据条件动态生成查询表达式。

使用场景:

  1. 适用于对数据库或其他外部数据源的查询操作,尤其是在使用Entity Framework或LINQ to SQL时。

优点:

  1. 查询优化:在数据库执行查询,可以生成SQL查询并利用数据库的索引、优化等特性。
  2. 处理大数据集:不需要将所有数据加载到内存中,可以只查询并返回所需的数据,减少内存占用。

缺点:

  1. 只能用于与数据库等外部数据源的查询,不能用于对内存中的集合进行操作。

An image

相关运算符

标准查询运算符

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运算符可用于转换序列(集合)中元素的类型。

转换运算符分为三种:

  1. As运算符(AsEnumerable和AsQueryable),
  2. To运算符(ToArray,ToDictionary,ToList和ToLookup)
  3. 转换运算符(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 查询的语法来进行数据操作。

特点:

  1. 面向内存数据:它适用于操作内存中的数据集合,如数组、列表、字典等。
  2. 延迟执行:大多数 LINQ 查询是延迟执行的,查询不会在定义时立即执行,而是在访问查询结果时才执行(例如,通过 foreach 遍历结果时)。
  3. 类型安全:LINQ 查询是类型安全的,编译时会进行类型检查,避免了运行时类型错误。
  4. 强大的查询功能:支持丰富的操作,如过滤、排序、投影、分组、连接、聚合等。

使用场景:

LINQ to Objects 适合处理在内存中的集合或数据结构,通常用于操作简单的集合,如数组、List、Dictionary 等。

常见的 LINQ 方法:

C#
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 查询,并在数据库服务器上执行,从而返回结果到应用程序中。

特点:

  1. 强类型查询:LINQ to Entities 查询是强类型的,编译时会进行类型检查,确保安全性和准确性。
  2. 对象映射到数据库:通过 EF,实体类(POCOs)会映射到数据库的表,查询返回的结果也是实体对象。
  3. 延迟执行:LINQ 查询默认是延迟执行的。即查询在被定义时不会执行,只有在遍历结果或调用操作时,查询才会真正执行并在数据库端运行。
  4. 自动生成 SQL:LINQ 查询会被 EF 自动翻译为 SQL 查询,并在数据库中执行,返回结果。

注意

  1. 支持的操作有限:由于 LINQ to Entities 最终会被转换为 SQL 查询,因此只能使用那些能够转换为 SQL 的方法和操作。一些复杂的 C# 代码逻辑可能无法直接翻译为 SQL 语句。
  2. 查询性能:虽然 LINQ to Entities 可以生成 SQL 查询,但生成的 SQL 并不总是最优的。在复杂查询或大数据量操作时,建议对生成的 SQL 进行分析和优化。

常见的 LINQ 方法:

C#
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)

参考并行循环

Document Base on VitePress.