C# 基础知识系列- 8 Linq最后一部分查询表达式语法实践

1 前言

之前的几篇文章介绍了Lambda和Linq的一些支持方法。这一篇我尝试通过模拟具体的业务场景来描述一下Linq的两种查询方式的使用。

一直提的Linq查询方式分为两种,一种就是方法链的形式,官方的称呼是流式查询;另一种是类似于SQL语句的查询方式,我之前叫做类SQL查询方式,不过有的文档称之为查询表达式。
注意,本篇内容需要先看过 《C# 基础系列-7》,并有一定的对象和集合的基础。

1.1 数据准备:

因为这篇内容会涉及到多个数据源,所以这里需要准备一些类和数据,以下数据纯属虚构,不涉及到现实。

/// <summary>
/// 学生
/// </summary>
public class Student
{
    /// <summary>
    /// 学号
    /// </summary>
    public long StudentId { get; set; }
    /// <summary>
    /// 姓名
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 年龄
    /// </summary>
    public int Age { get; set; }
    /// <summary>
    /// 班级
    /// </summary>
    public string Class { get; set; }
}
/// <summary>
/// 科目
/// </summary>
public class Subject
{
    /// <summary>
    /// 
    /// </summary>
    public long SubjectId { get; set; }
    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 年级
    /// </summary>
    public string Grade { get; set; }
    /// <summary>
    /// 教师
    /// </summary>
    public string Teacher { get; set; }
}

/// <summary>
/// 考试
/// </summary>
public class Exam
{
    /// <summary>
    /// 考试编号
    /// </summary>
    public long ExamId { get; set; }
    /// <summary>
    /// 科目编号
    /// </summary>
    public long SubjectId { get; set; }
    /// <summary>
    /// 学生编号
    /// </summary>
    public long StudentId { get; set; }
    /// <summary>
    /// 分数
    /// </summary>
    public double Score { get; set; }
    /// <summary>
    /// 考试时间:年-月 如202004 表示2020年04月
    /// </summary>
    public int Time { get; set; }
}

数据源:

List<Student> students = new List<Student>();// 学生列表,忽略数据来源
List<Subject> subjects = new List<Subject>();// 科目列表,忽略数据来源
List<Exam> exams = new List<Exam>();// 考试列表,忽略数据来源

2 查询演示

预先介绍一个概念,C#中有一种类型叫做匿名类型。因为C#的要求是万物皆对象,对象皆有类,所以每一个对象或者数据都是有类型在背后支撑的。但是有时候会需要一些一次性的只读类型,这时候声明一个完整的类就有点得不偿失了。什么是一次性的只读类型呢,就是我们只关心它有哪些属性,不关心它有什么方法,同时这个类对应的对象只能在初始化的时候给属性赋值其他时候不能重新赋值,而且这个类型只在方法内部使用,在这个变量使用完成之后这个类型也失去了意义,这种类型就是我们所说的一次性的只读类型。

那么这种类型怎么声明呢?先不急,先再介绍一个关键字var。这个关键字有什么特别的地点吗?var 表示隐式“类型”,意思就是用var声明的变量其类型需要编译器自己结合上下文推断,也就是说使用者和声明者都知道这个变量的类型,但是没有明说。

那么为什么需要介绍var呢?原因在于,var 是匿名对象的基础。因为匿名对象不能用object声明变量,原因有两点,第一,变量声明为object之后,我们所需要的属性就无法使用了;第二,匿名类型的对象无法直接类型转换为object。所以,想要正常使用匿名类型,必须用var

下面简单演示一下匿名类型的声明和使用:

var obj = new 
{
    Name = "萧三",
    Age = 20
};
// obj.Name 萧三
// obj.Age 20

这就是匿名类型,声明了一个有Name和Age属性的对象,这个对象我们知道它有哪些属性,但是不知道它的类型是什么。

在介绍完需要的知识后,将通过实际的情况来比较一下流式查询和查询表达式两种写法。

2.1 简单查询

  1. 查询班级是三年一班的所有同学

    // 流式查询
    var results = students.Where(t=>t.Class=="三年一班");
    // 查询表达式
    var results = from s in students
        where s.Class == "三年一班"
        select s;
    
    

    这两种查询方式结构都是IEnumerable<T>。

  2. 获取姓张的所有学生的花名册

    // 流式查询
    var results = students.Where(t=>t.Name.StartWith("张"));
    // 查询表达式
    var results = from s in students where s.Name.StartWith("张") select s;
    
  3. 按班级进行分组获取每个班的学生花名册

    // 流式查询
    var results = students.GroupBy(t => t.Class);
    // 查询表达式
    var results = from s in students group s by s.Class;
    // 注明:完整的写法如下:
    // var results = from s in students group s by s.Class into g select g;
    

    需要注意的是,如果返回结果是一个分组的结果,那么就不用select了。

  4. 查询每个班的平均年龄

    // 流式查询
    var results = students.GroupBy(t => t.Class)
                    .Select(t => new {Class = t.Key, AveAge = t.Average(p => p.Age)});
    // 查询表达式
    var results = from s in students
                    group s by s.Class
                    into g
                    select new {Class = g.Key, AveAge = g.Average(t => t.Age)};
    

    查询表达式中没有统计查询的相关关键字,只能通过方法来获取,同时查询表达式返回的是一个集合,所以没法直接通过查询表达式进行求和、求平均等。

  5. 对所有学生按照年龄大小从大到小进行排序

    // 流式查询
    var results = students.OrderByDescending(t => t.Age);
    // 查询表达式
    var results = from s in students orderby s.Age descending select s;
    
  6. 对所有学生按照年龄大小从小到大进行排序

    // 流式查询
    var results = students.OrderBy(t => t.Age);
    // 查询表达式
    var results = from s in students orderby s.Age select s;
    
  7. 先按年龄排序再按姓名进行排序

    // 流式查询
    var results = students.OrderBy(t => t.Age).ThenBy(t=>t.Name);//ThenByDescending 是降序版的ThenBy
    // 查询表达式
    var results = from s in students orderby s.Age //descending 如果是降序则增加这个关键字
        , s.Name select s;
    

2.2 复杂查询

前一部分介绍了简单的查询,这一部分介绍联合多个数据源进行一系列的查询操作。

  1. 查询三年级语文科目在202004月举行的考试的所有成绩

    // 流式查询
    var results = subjects.Join(exams, s => s.SubjectId, e => e.StudentId, (s, e) => new
    {
        s.Name,
        s.Grade,
        e.Score,
        e.Time
    }).Where(t=>t.Grade == "三年级" && t.Name =="语文" && t.Time == 202004).Select(t=>t.Score);
    // 查询表达式
    var results = from s in subjects
                    join e in exams on s.SubjectId equals e.SubjectId
                    where e.Time == 202004 && s.Grade == "三年级" && s.Name == "语文"
                    select e.Score;
    
  2. 按年级进行分组,查询各年级语文分数

    // 流式查询
    var results = subjects.Where(p => p.Name == "语文")
                    .Join(exams, s => s.SubjectId, e => e.SubjectId, (s, e) => new {s.Grade, e.Score})
                    .GroupBy(t => t.Grade);
    // 查询表达式
    var results = from s in subjects
                    join e in exams on s.SubjectId equals e.SubjectId
                    where s.Name == "语文"
                    group e.Score by s.Grade
                    into g
                    select g;
    
  3. 求各年级历次考试各科目分数的平均分和最高分以及最低分

    //流式查询
    var results = subjects.Join(exams, s => s.SubjectId, e => e.SubjectId, (s, e) => new
                {
                    s.Grade,
                    s.Name,
                    e.Score
                }).GroupBy(t => t.Grade).Select(t => new
                {
                    Grade = t.Key,
                    Subjects = t.GroupBy(p => p.Name).Select(p => new
                    {
                        Name = p.Key,
                        Max = p.Max(r => r.Score),
                        Min = p.Min(r => r.Score),
                        Average = p.Average(r => r.Score)
                    })
                });
    //查询表达式
    var results = from s in subjects
                    join e in exams on s.SubjectId equals e.SubjectId
                    let o = new {s.Grade, s.Name, e.Score}
                    group o.Score by new {o.Grade, o.Name}
                    into o
                    let p = new
                    {
                        o.Key.Grade, Subject = new {o.Key.Name, 
                                                    Max = o.Max(),
                                                    Min = o.Min(), 
                                                    Average = o.Average()}
                    }
                    group p.Subject by p.Grade
                    into g
                    
                    select new
                    {
                        Grade = g.Key,
                        Subjects = g.AsEnumerable()
                    };
    

    以上大概介绍了一下Linq的使用,明显可以看得出,流式查询和查询表达式在可读性上区别还是蛮大的。对于熟悉SQL的人,查询表达式能更快的上手;对于我来说,更习惯于用流式查询,不过在多数据源联合的时候,我更倾向于写查询表达式。以上是基础篇Linq的全部内容。

更多内容烦请关注我的博客

C# 基础知识系列- 8 Linq最后一部分查询表达式语法实践

C# 基础知识系列- 8 Linq最后一部分查询表达式语法实践

上一篇:C#中使用JsonConvert解析JSON


下一篇:Ubuntu: Mount The Drive From Command Line