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


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

    为什么要使用LINQ

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

    例如我们想要找出一个IEnumerable中所有大于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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第二种方案:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    那么这两种方案的区别是什么?第一种方案是把所有元素全部检查一遍,把符合要求的元素放到List 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}";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    然后再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 });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Where方法

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

    IEnumerable<Employee> res = lst.Where(e => e.Age > 20); //把年龄大于20的返回过来
    
    foreach (Employee e in res)
        Console.WriteLine(e);
    
    • 1
    • 2
    • 3
    • 4

    返回结果

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Count方法

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

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

    返回结果

    2
    
    • 1

    Any方法

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

    如以下代码

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

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

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

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

    同样的,我们使用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);	//正确,仅一条数据满足条件
    
    • 1
    • 2

    SingleOrDefault方法

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

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

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

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

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

    First方法

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

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

    输出结果

    ID=1, Name=jerry, Age=28, Gender=True, Salary=5000
    
    • 1

    FirstOrDefault方法

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

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

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

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

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

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

    排序方法

    一般排序

    排序方法有两种

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

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

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

    其输出结果为

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

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

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

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

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

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

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

    输出如下:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    限制结果集的方法

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

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

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

    其输出结果如下:

    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
    
    • 1
    • 2
    • 3

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

    Take单独使用

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

    输出:

    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
    
    • 1
    • 2
    • 3

    Skip单独使用

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

    输出:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    聚合函数(方法)

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

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

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

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

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

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

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

    GroupBy方法

    该方法用于对数据分组,其参数是分组条件表达式,返回值为IGrouping类型的泛型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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其输出结果为:

    年龄为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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    投影与匿名类型

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

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

    输出结果为:

    女
    女
    男
    男
    
    • 1
    • 2
    • 3
    • 4

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

    var items = lst.Select(e => new
    {
        XingMing = e.Name,
        NianLing = e.Age,
        Xingbie = e.Gender ? "男" : "女"
    });
    foreach (var item in items)
        Console.WriteLine(item);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其输出结果为:

    { 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 = 男 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    类型转换

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

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

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

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

    链式调用

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

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

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

    例如我们定义数组

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

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

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

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

    查询语法

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

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

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

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

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

    var items = from e in nums
                where e < 3
                orderby e
                select e;
    
    • 1
    • 2
    • 3
    • 4

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

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

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

    结束

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

    查询语法

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

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

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

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

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

    var items = from e in nums
                where e < 3
                orderby e
                select e;
    
    • 1
    • 2
    • 3
    • 4

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

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

    [外链图片转存中…(img-DvhrkVqA-1662208913316)]

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

    结束

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

  • 相关阅读:
    大数据必学Java基础(一百一十一):过滤器注解应用和开发案例
    Flask包算法服务
    工程化分类面试题
    LSTM的预测算法 - 股票预测 天气预测 房价预测 计算机竞赛
    创建第一个鸿蒙开发项目
    云原生大数据平台零信任网络安全实践技术稿
    uniapp:如何修改路由加载条的样式
    【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶
    springboot 多线程实现
    FFmpeg 基础
  • 原文地址:https://blog.csdn.net/qq_42759112/article/details/126682237