优秀的编程知识分享平台

网站首页 > 技术文章 正文

一篇文章搞懂C#中LINQ查询

nanyue 2025-02-10 13:40:30 技术文章 4 ℃

链接:学英语_学C-Sharp: 学英语学c#系列视频代码源码

LINQ (Language Integrated Query)直译过来—“语言整合查询”。是 C# 中的一项重要特性,它提供了一种强大而统一的方式来查询和操作不同数据源,包括对象集合、数据库、XML文档等。查询是一种从数据源检索数据的表达式, 传统的数据查询往往基于自身的查询语句,例如,用于关系数据库的 SQL 和用于 XML 的 XQuery。 程序员每查询一种类型的数据源,都必须选择与之对应的查询语法,学习成本很高。LINQ 通过将查询能力直接融入到语言本身,查询数据就像使用类和方法一样便捷,极大地提高了代码的可读性和简洁性。

Linq工作流程

如图所示,您可以使用任何.NET支持的编程语言编写LINQ查询,如C#、VB.NET、J#、F#等。

LINQ提供程序是位于LINQ查询和数据源之间的软件组件。LINQ提供程序将把LINQ查询转换成基础数据源可以理解的格式。例如,LINQ到SQL提供程序会将LINQ查询转换为SQL Server数据库可以理解的SQL语句。类似地,LINQ到XML提供程序将把查询转换成XML文档可以理解的格式

什么时候使用Linq

  • 查询集合:LINQ非常适合查询集合,如数组、列表或任何其他实现IEnumerable的类型。它简化了过滤、排序和分组数据的过程。
  • 数据库操作:使用LINQ到SQL或实体框架,您可以执行数据库操作。LINQ查询会自动转换为SQL查询,从而更容易与数据库交互,而无需编写原始SQL。
  • 可读性和可维护性:与传统的循环和条件语句相比,LINQ查询通常会产生更具可读性和可维护性的代码。语法是声明性的,指定您想要做什么,而不是如何做。
  • 使用XML:LINQ to XML提供了一种处理XML文档的简单而有效的方法。它允许您以更易读、更简洁的方式查询、修改和导航XML数据。
  • 连接数据源:如果您需要连接来自不同来源(如不同的集合、数据库或XML文件)的数据,LINQ可能是一个强大的工具。它简化了连接和关联来自多个来源的数据的语法。
  • 聚合和计算:当您需要对一组项目执行计算或聚合(如总和、平均值、最小值、最大值)时,LINQ提供了简单的方法来完成这些任务。
  • 转换数据类型:LINQ提供了将一种类型的数据转换为另一种类型的易于使用的方法,例如将数组转换为列表,反之亦然。

Linq优缺点

优点:学习Linq一种语法就查询多种数据源;代码简洁;使用visual stuodio会帮助检查代码错误;linq内置了很多方法来过滤/分组/聚合/求均值;代码可以复用;

缺点:对于Linq写复杂的SQL语句比较麻烦;没有充分发挥数据库存储过程的优势;使用Linq不恰当的时候性能很糟糕;如果查询发生变化时,需要重新编译C#代码.

查询操作的三个部分

所有 LINQ 查询操作都由以下三个不同的操作组成:获取数据源; 创建查询; 执行查询

Linq两种查询语法

查询语法(Query Expression Syntax)

查询语法类似于 SQL 查询语句(实际上我也用的不好),使用熟悉的关键词(如 fromwhereselectorderbygroupbyjoinletintoinonequalsbyascendingdescending 等)来构建查询表达式。基本语法如下

以下是一些基本示例:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //Step1: Data Source
            List integerList = new List()
            {
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10
            };

            //Step2: Query
            //LINQ Query using Query Syntax to fetch all numbers which are > 5
            var QuerySyntax = from obj in integerList //Data Source
                              where obj > 5 //Condition
                              select obj; //Selection

            //Step3: Execution
            foreach (var item in QuerySyntax)
            {
                Console.Write(item + " ");
            }

            Console.ReadKey();
        }
    }
}

方法语法(Method-Based Syntax)

方法语法通过调用 LINQ 扩展方法实现查询,这些方法通常采用 Lambda 表达式作为参数。虽然写法与查询语法不同,但两者在功能上完全等价。方法语法更适用于复杂的查询条件或需要与非查询相关的 .NET 方法链式调用的情况。示例图片如下:

下面是对应上述查询语法的例子:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //Step1: Data Source
            List integerList = new List()
            {
                1, 2, 3, 4, 5, 6, 7, 8, 9, 10
            };

            //Step2: Query
            //LINQ Query using Query Syntax to fetch all numbers which are > 5
            var QuerySyntax = integerList.Where(obj => obj > 5).ToList(); 

            //Step3: Execution
            foreach (var item in QuerySyntax)
            {
                Console.Write(item + " ");
            }

            Console.ReadKey();
        }
    }
}

Linq混合语法**

将查询语法和方法语法混用,示意图如下图所示:

示例:

List people = new List
{
    new Person { Name = "Alice", Age = 30, City = "New York" },
    new Person { Name = "Bob", Age = 27, City = "Los Angeles" },
    new Person { Name = "Charlie", Age = 3?, City = "Chicago" },
    new Person { Name = "David", Age = 45, City = "San Francisco" },
};

var queryResult1 = from person in people
                   where person.Age >= 30 && person.City.StartsWith("N")
                   select new Result
                   {
                       FullName = $"{person.Name} ({person.Age})",
                       Location = person.City
                   };

var queryResult2 = people.Where(p => p.Age < 30 && p.City.EndsWith(" Angeles"))
                        .Select(p => new Result
                        {
                            FullName = $"{p.Name} ({p.Age})",
                            Location = p.City
                        });

var results = queryResult1.Concat(queryResult2);

foreach (var result in results)
{
    Console.WriteLine($"{result.FullName} lives in {result.Location}");
} 

class Person
{
    public string Name { get; set; }
    public double Age { get; set; }
    public string City { get; set; }
}

在这个示例中:

  1. 查询语法 用于筛选年龄大于等于 30 岁且城市名称以 "N" 开头的人,并将他们转换为一个新的匿名类型,包含全名(姓名+年龄)和所在城市。
from person in people
where person.Age >= 30 && person.City.StartsWith("N")
select new
{
    FullName = $"{person.Name} ({person.Age})",
    Location = person.City
}

方法语法 用于筛选年龄小于 30 岁且城市名称以 "Angeles" 结尾的人,并同样转换为新的匿名类型。

people.Where(p => p.Age < 30 && p.City.EndsWith(" Angeles"))
      .Select(p => new
      {
          FullName = $"{p.Name} ({p.Age})",
          Location = p.City
      })

混合语法

体现在将上述两个查询结果使用 Concat 方法合并为一个单一的结果序列:

var results = queryResult1.Concat(queryResult2);

最终的 results 变量包含了符合两个不同条件的所有人的信息。在循环中,我们遍历并打印出每个人的名字、年龄和所在城市,展示了如何使用 LINQ 的混合语法来执行复杂的查询操作。这种混合使用方式允许开发人员根据需要选择最适合特定查询片段的语法形式,从而保持代码的清晰度和可读性。

方法语法和查询语法比较

绝大多数时候,两种查询方式都能满足业务需求,使用那种查询取决于个人和团队偏好。

两者之间没有性能差异,因为查询语法在编译时被转换成方法语法。

对于复杂的语法,方法查询更加强大和灵活,但对于更简单的查询或具有SQL背景的查询,查询语法可能更直观。


IEnumerable和 IQueryable

C# 中的 IEnumerable 和 IQueryable 用于保存数据集合,并根据业务需求执行数据操作,如筛选、排序、分组等。

IEnumerable 在不应用筛选器的情况下从数据库中提取记录。但是 IQueryable 通过应用筛选器从数据库中提取记录。

我们先通过navicat 中通过sql脚本,在MySql数据中创建一张表,如下图所示:

我们通过c#连接上MySq,让后就可以通过下面的方式访问数据库,其中DBContext是数据库上下文类,用来配置与MySql的连接,需要使用到EF core,我会在以后的教程中专门说。

我们看怎么通过IEnumerable访问数据库:

IEnumerable listStudents = DBContext.Students.Where(x => x.Gender == "Male");

我们再来看怎么通过 IQueryable的访问数据库,和上面对比,只是做了一个IEnumerable转换为IQueryable。

                IQueryable listStudents = DBContext.Students
                                   .AsQueryable()
                                   .Where(x => x.Gender == "Male");

区别

IEnumerable:主要用于查询和操作内存中的集合,如数组、列表和与 IEnumerable 兼容的集合。专为查询内存中已有的数据而设计。

适用于处理整个数据集可以放入内存中的中小型内存中集合。

IQueryable:用于从可能不在内存中的外部数据源(如数据库、Web Service 或远程数据存储)查询数据。专为查询驻留在应用程序内存之外的数据而设计。适用于处理大型数据集或应用程序外部的数据源,例如数据库。

LINQ查询的操作符

映射运算符:这些运算符将序列的元素转换为新形式。常见的投影运算符包括 Select 和 SelectMany。

Select :将序列的每个元素投影到新窗体中。

SelectMany:将每个序列元素投影到 IEnumerable,并将生成的序列拼展成一个序列。


过滤运算符:这些用于筛选数据。最常见的限制运算符是 Where,它将谓词应用于序列的每个元素并返回满足条件的谓词。

where:根据条件筛选一系列值。

OfType:根据指定类型筛选数组的元素。


分区运算符:这些运算符将序列分为两部分,并返回其中一部分。示例包括 Take、Skip、TakeWhile 和 SkipWhile。

Take:从序列的开头返回指定数量的连续元素。

Skip:绕过序列中指定数量的元素,然后返回其余元素。

TakeWhile:只要指定的条件为 true,则从序列中返回元素。

SkipWhile:只要指定条件为 true,就会绕过序列中的元素,然后返回其余元素。


排序运算符:这些运算符排列序列的元素。常见的排序运算符包括 OrderBy、OrderByDescending、ThenBy 和 ThenByDescending。

OrderBy:根据键按升序对序列的元素进行排序。

OrderByDescending:根据键按降序对序列的元素进行排序。

OrderByDescending:按升序对元素按顺序执行后续排序。

ThenByDescending:按降序对元素按序列执行后续排序。

Reverse**:**反转序列中元素的顺序。


分组运算符:这些运算符根据指定的键值对序列的元素进行分组。最值得注意的分组运算符是 GroupBy。

GroupBy:根据指定的键选择器功能对序列的元素进行分组。


连接运算符:这些运算符用于组合来自两个或多个序列的元素。常见的联接运算符是 Join 和 GroupJoin。

Join:根据匹配的键联接两个序列。

GroupJoin:根据键对序列中的元素进行分组,并将它们与另一个序列中的元素联接。


集合运算符:这些运算符对序列(如 Distinct、Union、Intersect 和 Except)执行数学集运算。

Distinct:从序列中删除重复元素。

Union:生成两个序列的集合并集。

Intersect :生成两个序列的集合交集。

Except:生成两个序列的集合差。


转换运算符:它们用于将一种类型的序列或集合转换为另一种类型的序列或集合。示例包括 ToArray、ToList、ToDictionary 和 AsEnumerable。

AsEnumerable:将 IEnumerable 强制转换为 IEnumerable

ToArray:将序列转换为数组。

ToList:**将序列转换为 List

ToDictionary :根据键选择器功能将序列转换为 Dictionary


元素运算符:这些运算符从序列中返回单个元素。示例包括 First、FirstOrDefault、Last、LastOrDefault、Single、SingleOrDefault 和 ElementAt。

First:返回序列的第一个元素。

FirstOrDefault:返回序列的第一个元素,如果未找到任何元素,则返回默认值。

Last:返回序列的最后一个元素。

LastOrDefault:返回序列的最后一个元素,如果未找到任何元素,则返回默认值。

Single:返回序列中的唯一元素,如果序列中没有恰好一个元素,则引发异常。

SingleOrDefault:如果序列为空,则返回序列的唯一元素或默认值;如果序列中有多个元素,此方法将引发异常。

ElementAt:返回序列中指定索引处的元素。

ElementAtOrDefault:返回序列中指定索引处的元素,如果索引超出范围,则返回默认值。


数量运算符:这些运算符返回一个布尔值,该值指示序列的所有或任何元素是否满足条件。示例包括 All、Any 和 Contains。

Any :确定序列的任何元素是否满足条件。

All:确定序列的所有元素是否都满足条件。

Contains:确定序列是否包含指定的元素。

聚合运算符:这些运算符对序列执行计算并返回单个值。示例包括 Count、Sum、Min、Max、Average 和 Aggregate。

Count:对序列中的元素进行计数。

LongCount:**对序列中的元素进行计数,并将计数作为 long。

Sum:计算数值序列的总和。

Min:返回序列中的最小值。

Max:返回序列中的最大值。

Average :计算数值序列的平均值。

Aggregate:在序列上应用累加器函数。

相等运算符:这些运算符用于比较序列的相等性。一个例子是 SequenceEqual。

SequenceEqual:**通过使用元素类型的默认相等比较器来比较元素,确定两个序列是否相等。


生成操作符:这些运算符用于创建新的值序列。示例包括 Range、Repeat 和 Empty。

Empty:**返回具有指定类型参数的空 IEnumerable

Repeat :生成包含一个重复值的序列。

Range:生成指定范围内的整数序列。

实例

查询中涉及的实体类,没有放到本篇文章中,可以到我的代码开源仓库中下载,地址:
https://gitee.com/sunny_1997/learn-English-learn-c-sharp

using Linq查询;


// 初始化 Employee 集合
var employees = new List
{
    new Employee(1,"张三",18,"女"),
    new Employee(2,"李四",28,"女"),
    new Employee(1,"无法",18,"女"),
    new Employee(2,"两列四",98, "男"),
    new Employee(1,"刘是否",4,"女"),
    new Employee(2,"赵佳佳",28, "男"),
    new Employee(1,"孙行者",38, "男"),
    new Employee(2,"程家山",19, "男"),
    // ... 其他员工 ...
};

// 查询所有年龄大于 25 岁的员工姓名
var namesOfOlderEmployees = from e in employees
                            where e.Age > 25
                            select e.Name;

// 对员工按年龄降序排列
var employeesOrderedByAge = from e in employees
                            orderby e.Age descending
                            select e;

// 分组员工按性别,并计算每个组的平均年龄
var averageAgesByGender = from e in employees
                          group e by e.Gender into g
                          select new { Gender = g.Key, AverageAge = g.Average(e => e.Age) };

List books = new List();
books.Add(new Book { id = Guid.NewGuid(), name = "西游记",         price = 25.0f });
books.Add(new Book { id = Guid.NewGuid(), name = "红楼梦",         price = 35.1f });
books.Add(new Book { id = Guid.NewGuid(), name = "水浒传",         price = 15.0f });
books.Add(new Book { id = Guid.NewGuid(), name = "三国演义",       price = 34.0f });
books.Add(new Book { id = Guid.NewGuid(), name = "三体",           price = 55.7f });
books.Add(new Book { id = Guid.NewGuid(), name = "我的朋友叫安妮", price = 4.0f  });
books.Add(new Book { id = Guid.NewGuid(), name = "隔壁的胡安",     price = 15.7f });
books.Add(new Book { id = Guid.NewGuid(), name = "老家的柳树",     price = 20f   });

{
    var result1 = books.Where(b => b.price < 21).Select(b => b);
    var results2 = books.Select(b => b.price < 21);
    var results3 = books.Where(b => b.price < 30).Select(b => new { Id = b.id ,Name = b.name})
                   .OrderBy(b=>b.Name); 
}

List shoes = new List();
shoes.Add(new Shoes(Guid.NewGuid(),"ANTA",78));

var result3 = books.Filter(new Func( b =>b.price>20));//使用List:注意要把lambda表达式用小括号括起来

var result4= books.Filter2(new Func(b => b.price > 20)); //使用IEnumerable://注意要把lambda表达式用小括号括起来
foreach (var item in result4)
{
    Console.WriteLine(item.name);
}

List people = new List
{
    new Person { Name = "Alice", Age = 30, City = "New York" },
    new Person { Name = "Bob", Age = 27, City = "Los Angeles" },
    new Person { Name = "Charlie", Age = 3, City = "Chicago" },
    new Person { Name = "David", Age = 45, City = "San Francisco" },
};


// 混合语法示例
var queryResult1 = from person in people
                   where person.Age >= 30 && person.City.StartsWith("N")
                   select new Result
                   {
                       FullName = $"{person.Name} ({person.Age})",
                       Location = person.City
                   };

var queryResult2 = people.Where(p => p.Age < 30 && p.City.EndsWith(" Angeles"))
                        .Select(p => new Result
                        {
                            FullName = $"{p.Name} ({p.Age})",
                            Location = p.City
                        });

var results = queryResult1.Concat(queryResult2);

foreach (var result in results)
{
    Console.WriteLine($"{result.FullName} lives in {result.Location}");
}



List intList = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//Method Syntax
var OddNumbersWithIndexPosition = intList.Select((num, index) => new
                                    {
                                        Numbers = num,
                                        IndexPosition = index
                                    }).Where(x => x.Numbers % 2 != 0)
                                    .Select(data => new
                                    {
                                        Number = data.Numbers,
                                        IndexPosition = data.IndexPosition
                                    });
            
foreach (var item in OddNumbersWithIndexPosition)
{
    Console.WriteLine($"IndexPosition :{item.IndexPosition} , Value : {item.Number}");
}

var a = new
{
    FirstName = "John",
    LastName = "Doe",
    Age = 30,
    IsEmployed = true
};

Console.WriteLine(a.FirstName);



Tags:

最近发表
标签列表