19

一篇文章教你学会ASP.Net Core LINQ基本操作 - CairBin

 3 years ago
source link: https://www.cnblogs.com/cairbin/p/16653604.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一篇文章教你学会ASP.Net Core LINQ基本操作

为什么要使用LINQ

LINQ中提供了很多集合的扩展方法,配合lambda能简化数据处理。

例如我们想要找出一个IEnumerable<int>中所有大于10的元素,使用LINQ则可以这样写

static void Main(string[] args)
{
    int[] nums = new int[] { 3, 5, 6, 5, 10, 12, 14, 7 };
    IEnumerable<int> res = nums.Where(a => a > 10);
    foreach (int i in res)
        Console.WriteLine(i);
}

其中使用IEnumerableusing System.Collections.Generic;

使用Where方法要using System.Linq;,该方法会遍历每个元素然后去判断是否大于10

LINQ背后原理

为了解LINQ背后的原理,我们首先去实现一个简单的Where方法

第一种方案:

static IEnumerable<int> MyWhere1(IEnumerable<int> items, Func<int, bool> f)
{
    List<int> res = new List<int>();
    foreach (int i in items)
        if (f(i) == true)
            res.Add(i);
    return res;
}

第二种方案:

static IEnumerable<int> MyWhere2(IEnumerable<int> items, Func<int, bool> f)
{
    List<int> res = new List<int>();
    foreach (int i in items)
        if (f(i) == true)
            yield return i;
}

那么这两种方案的区别是什么?第一种方案是把所有元素全部检查一遍,把符合要求的元素放到List<int> res里面,然后返回res;然而第二种方案使用yield,是一种“流水线”方式处理,找到符合条件的元素立即返回,返回后Console.WriteLine立即能够打印,从而提高了数据处理效率。

LINQ的常用扩展方法

LINQ提供了很多扩展方法,大部分都在System.Linq命名空间中。

接下来准备一些数据,用于下面的操作。

首先定义一个员工类,里面有姓名工资等成员。

class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public bool Gender { get; set; }
    public int Salary { get; set; }
    public override string ToString()
    {
        return $"ID={Id}, Name={Name}, Age={Age}, Gender={Gender}, Salary={Salary}";
    }

}

然后再Main方法中创建实例导入数据,并将所有实例保存到列表中。

List<Employee> lst = new List<Employee>();
lst.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
lst.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
lst.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
lst.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
lst.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
lst.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
lst.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
lst.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });

Where方法

该方法会遍历每个元素然后去判断是否符合条件,符合条件的元素则被返回。

IEnumerable<Employee> res = lst.Where(e => e.Age > 20); //把年龄大于20的返回过来

foreach (Employee e in res)
    Console.WriteLine(e);
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

Count方法

该方法会返回符合条件的元素的个数

Console.WriteLine(lst.Count(e=>e.Salary>8000));
2

Any方法

该方法会判断是否存在至少一个元素符合条件。另外,如果传入的参数为空,则会判断IEnumerable(或者实现了IEnumerable接口的其他类,如List)是否存在元素。

如以下代码

List<int> test = new List<int>();
Console.WriteLine(test.Any());

由于列表为空,test.Any()返回的就是false

如果判断是否存在Employee类型的元素于lst中,则代码如下,其返回值为true

Console.WriteLine(lst.Any(e=>e.Salary>8000));

同样的,我们使用Count方法也可以达成此目的(判断返回元素个数是否为0),但是Count方法相对于Any方法效率较低

这是因为Any找到一个符合条件的元素会立即返回,而Count方法是统计个数,找到一个符合元素后还要继续向后找。

有关一条数据的方法

有关一条数据的方法有好几种,不过在细节上略有不一样,所以我们把他们放一块介绍。

方法 描述
Single() 有且只有一条满足要求的数据
SingleOrDefault() 最多只有一条满足要求的数据
First() 至少有一条,并且返回第一条
FirstOrDefault() 返回第一条或默认值

Signle方法

Employee elem = lst.Single(e => e.Salary > 8000);  //错误,有多条数据满足条件
Employee elem = lst.Single(e => e.Salary > 8500);	//正确,仅一条数据满足条件

SingleOrDefault方法

对于SingleOrDefault,当且仅当存在一条数据满足条件,返回该数据;如果存在多条则报错;如果不存在则返回默认值。

Employee elem = lst.SingleOrDefault(e=>e.Salary>8000);  //错误,有多条数据满足条件
Employee elem = lst.SingleOrDefault(e=>e.Salary>8500);	//正确,仅一条数据满足条件

下面我们来看下默认值的情况

int[] nums = new int[] { 1, 2, 3 };
int i = nums.SingleOrDefault(i => i > 10);
Console.WriteLine(i);

由于不存在大于10的整型数字,所以该方法返回变量i的默认值,输出结果为0

First方法

该方法要求数据至少有一条满足条件,并且返回查询到的第一条数据。

Employee test = lst.First(e=>e.Salary>9000);	//报错,不存在数据满足条件
//正确,满足年龄大于16的有多条,仅按照我们添加数据的顺序返回第一条
Employee test = lst.First(e=>e.Age>16);
Console.WriteLine(test);
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000

FirstOrDefault方法

该方法返回符合条件的第一条数据,否则返回默认值

如下方代码,从数组中返回一个大于2的整数,其输出结果为3

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };
int i = nums.FirstOrDefault(e => e > 2);
Console.WriteLine(i);

我们再来看看返回默认值的情况

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };
int i = nums.FirstOrDefault(e => e > 10);
Console.WriteLine(i);

由于数组中不存在大于10的数,所以i的值就是其默认值0

排序方法有两种

方法 描述
OrderBy() 正序排序
OrderByDescending() 逆序排序

二者用法几乎一致,此处仅演示OrderBy方法

IEnumerable<Employee> res2 = lst.OrderBy(e => e.Age);
foreach (Employee e in res2)
    Console.WriteLine(e);

其输出结果为

ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500

很显然数据已经按照年龄从小至大的顺序进行排序了。

此外应该注意的是,该方法必须有参数。如果想要对一个数组进行排序,正确写法如下

int[] nums2 = new int[] { 3, 1, 2, 4, 5, 6 };
IEnumerable<int> resNum = nums2.OrderBy(i => i);
//写成nums2.OrderBy()是错误的

所谓多排序,就是按照一个条件对数据进行排序后,存在多个数据该条件下的值一致,然后再对这些值一致的数据按照其他条件排序。

其方法也有两个,一般是在OrderBy()OrderByDescending()之后调用

方法 描述
ThenBy() 正序再排序
ThenByDescending() 逆序再排序

我们对文章上方的数据首先按照对年龄进行排序,然后对年龄一致的员工再按照工资进行逆排序,编写代码如下:

IEnumerable<Employee> sortTest = lst.OrderBy(x => x.Age).ThenByDescending(x => x.Salary);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

输出如下:

ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000

限制结果集的方法

限制结果集,获取部分数据的方法一般是利用SkipTake方法

例如我想要从上述员工数据中,获取从第2条开始连续的3条数据,则代码可以这样写;

IEnumerable<Employee> sortTest = lst.Skip(2).Take(3);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);

其输出结果如下:

ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000

当然,Skip方法和Take方法也可以单独使用:

Take单独使用

IEnumerable<Employee> sortTest = lst.Take(3);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=3, Name=lily, Age=35, Gender=False, Salary=9000

Skip单独使用

IEnumerable<Employee> sortTest = lst.Skip(2);
foreach (Employee emp in sortTest)
    Console.WriteLine(emp);
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

聚合函数(方法)

LINQ聚合函数常用的有这些,但应当注意的是它们的返回值类型不与其他LINQ的方法一样是IEnumerable而是条件的值的类型

方法 描述
Max() 返回给定条件的最大值
Min() 返回给定条件的最小值
Average() 返回给定条件的平均值
Sum() 返回给定条件的和
Count() 统计满足条件的数据的个数

这些方法用法大致相同,甚至Count方法在上文中已经介绍过,此处仅用Max方法演示

int maxSalary = lst.Max(x => x.Salary);
Console.WriteLine(maxSalary);

该样例会输出所有员工的最大工资(请注意maxSalary的类型)

如果想要找到大于30岁的员工的最高工资,则可以

int maxSalary = lst.Where(x=>x.Age > 30).Max(x => x.Salary);

GroupBy方法

该方法用于对数据分组,其参数是分组条件表达式,返回值为IGrouping<TKey, TSource>类型的泛型IEnumerable。

我们编写代码来实现很具年龄分组:

IEnumerable<IGrouping<int, Employee>> items = lst.GroupBy(x => x.Age);
foreach (var item in items)
{
    Console.WriteLine($"年龄为{item.Key}的分组成员有:");
    foreach (var i in item)
        Console.WriteLine(i);
    Console.WriteLine();
}

其输出结果为:

年龄为28的分组成员有:
ID=1, Name=jerry, Age=28, Gender=True, Salary=5000

年龄为33的分组成员有:
ID=2, Name=jim, Age=33, Gender=True, Salary=3000
ID=8, Name=jack, Age=33, Gender=True, Salary=8000

年龄为35的分组成员有:
ID=3, Name=lily, Age=35, Gender=False, Salary=9000
ID=6, Name=nancy, Age=35, Gender=False, Salary=8000
ID=7, Name=zack, Age=35, Gender=True, Salary=8500

年龄为16的分组成员有:
ID=4, Name=lucy, Age=16, Gender=False, Salary=2000

年龄为25的分组成员有:
ID=5, Name=kimi, Age=25, Gender=True, Salary=1000

通过打印我们可以发现IEnumerable元素为IGrouping类型,其键与值对应关系是一对多的。在这里每个元素的键就是年龄,而值为具有相同年龄的Employee类型的员工数据。

投影与匿名类型

投影是把集合中每一项转化为另外一种类型。

IEnumerable<string> items = lst.Where(x => x.Salary > 5000).Select(x => x.Gender ? "男" : "女");
foreach (var item in items)
    Console.WriteLine(item);

输出结果为:

女
女
男
男

匿名类型没有名称,所以我们没有办法去用类型名去声明它,而是需要用到var关键字

var items = lst.Select(e => new
{
    XingMing = e.Name,
    NianLing = e.Age,
    Xingbie = e.Gender ? "男" : "女"
});
foreach (var item in items)
    Console.WriteLine(item);

其输出结果为:

{ XingMing = jerry, NianLing = 28, Xingbie = 男 }
{ XingMing = jim, NianLing = 33, Xingbie = 男 }
{ XingMing = lily, NianLing = 35, Xingbie = 女 }
{ XingMing = lucy, NianLing = 16, Xingbie = 女 }
{ XingMing = kimi, NianLing = 25, Xingbie = 男 }
{ XingMing = nancy, NianLing = 35, Xingbie = 女 }
{ XingMing = zack, NianLing = 35, Xingbie = 男 }
{ XingMing = jack, NianLing = 33, Xingbie = 男 }

在实际使用中,我们往往不是一定用IEnumerable,还有可能是List等,所以需要用到类型转换

例如我们利用Where方法返回工资大于6000的员工存放到IEnumerable中,然后将其转化为List类型

List<Employee> lst2 = lst.Where(e => e.Salary > 6000).ToList();

此外还有ToArray等方法,此处不过多说明。

所谓链式调用就是调用完一个函数(方法)后还能再后面继续跟着调用其它函数(方法)。

由于LINQ绝大多数方法返回的都是IEnumerable方法,而且绝大部分都是针对IEnumerable接口,所以可以在调用方法后继续调用其他方法。

lst.Where(e => e.Salary > 6000).ToList();

例如我们定义数组

int[] nums = new int[] { 1, 2, 3, 4, 5, 6 };

在小于3的元素中选取最大值

int a = nums.Where(x=>x<3).Max();

对于上面这行nums.Where(x=>x<3).Max()Where方法后加一个点然后再调用Max方法的形式就叫做链式调用。

对于上述的使用WhereSelect等扩展方法进行数据查询的写法叫做LINQ方法语法

然而还有一种叫做查询语法

我们同样定义一个数组演示

int[] nums = new int[] { 6,5,4,3,2,1 };

我们取小于3的元素,然后进行正序排序,则用查询语法则可以如下:

var items = from e in nums
            where e < 3
            orderby e
            select e;

这里需要注意,查询语法需要以select或group子句结尾

那么问题来了,方法语法与查询语法有什么区别?我们可以用方法语法写一段相同效果的语法,然后用反编译器(ILSpy)去看一下代码。

20220903203448.png

其反编译结果给出了查询语法的形式,然后对查询语法生成文件进行反编译,发现结果相同,这说明两种方法在编译后没有任何区别只是写法不同

LINQ的基本操作大致就这些,感谢杨中科老师提供的课程


Recommend

  • 99
    • zhuanlan.zhihu.com 7 years ago
    • Cache

    一篇文章学会使用压缩器

  • 90
    • 掘金 juejin.im 7 years ago
    • Cache

    git基本操作,一篇文章就够了!

    1. git简介 在实际开发中,会使用git作为版本控制工具来完成团队协作。因此,对基本的git操作指令进行总结是十分有必要的,本文对一些术语或者理论基础,不重新码字,可以参考廖雪峰老师的博文,本文只对命令做归纳总结。 git的通用操作流程如下图(来源于网络)

  • 65

    会员和积分是用户体系的孪生兄弟,两者经常同时出现。但是,这并不意味着会员和积分是同一个概念,事实上两者可以相互独立存在。上一篇文章我们讲到了会员体系的定义和建立方法,本文将主要阐述积分体系和会员体系的异同点以及如何建立积分体系。 一、什么是积分体...

  • 52

    会员积分体系在如今的产品中非常常见,然而对于从未接触过的产品、运营来说,首次建立会员积分体系时往往会觉得无从下手,我自己也是从一个资深小白一步步总结出自己的套路。接下来,我会通过系列文章分享建立会员积分体系的一些经验想法,本篇文章主要介绍如何从0...

  • 10
    • www.androidchina.net 4 years ago
    • Cache

    一篇文章,教你学会Git

    在日常工作中,经常会用到Git操作。但是对于新人来讲,刚上来对Git很陌生,操作起来也很懵逼。本篇文章主要针对刚开始接触Git的新人,理解Git的基本原理,掌握常用的一些命令。 一、Git工作流程 以上包括一些简单而...

  • 16

    在我们日常工作中,代码写着写着就出现下列的一些臭味。但是还好我们有SOLID这把‘尺子’, 可以拿着它不断去衡量我们写的代码,除去代码臭味。这就是我们要学习SOLID原则的原因所在。 设计的臭味 僵化性 具有联动性,...

  • 6
    • www.51cto.com 3 years ago
    • Cache

    一篇文章学会什么是Kubernetes

    一篇文章学会什么是Kubernetes-51CTO.COM 一篇文章学会什么是Kubernetes 作者:乔克 2022-08-04 09:39:39 Kubernetes是一个声明式系统,声明式系统和命令式系统​是有本质的区别。所...

  • 7
    • www.cnblogs.com 3 years ago
    • Cache

    递推递归与排列组合 - CairBin

    递推递归与排列组合 排列组合问题在暴力枚举的情况一般有3种情况 我们在此记个数为N 情况一:打印n个数的全排列: N=n!N=n!

  • 3
    • www.cnblogs.com 3 years ago
    • Cache

    ASP.Net Core异步编程 - CairBin

    ASP.Net Core异步编程 什么是异步编程? 异步编程是可以让程序并行运行的一种手段,其可以让程序中的一个工作单元与主应用程序线程分开独立运行,并且在工作单元运行结束后,会通知主应用程序线程它的运行结果或者失败原...

  • 9

    【Java】一篇文章教你学会异常! 精选 原创 cuican233 2022-10-13 11:32:58

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK