C#的lambda表达式、Linq、以及常用方法
Lambda:简单来讲就是匿名函数,我们不声明方法名,只写一个方法体,这个方法体就是lambda表达式
Linq:Linq是 Language Intergrated Query
(语言集成查询)的缩写,可以对本地对象**集合**或者远程数据源进行结构化的查询操作。
首先,在写lambda表达式之前,需要先了解 两个特殊的类型:Func
和Action
。
这是两个委托,这里先不急着了解什么是委托(详细讲解请查看:C# 委托 与 事件_c# 事件与委托-CSDN博客),可以把它们当做一种名称规范就行,它们都可以表示一个方法。不同的是其中Func
表示一个有返回值的方法,Action
表示一个没有返回值的方法。C#对这两个的定义如下:
public delegate TResult Func();//注意这里的out 表示这个泛型是返回值的类型泛型
public delegate void Action();
其中Func
和Action
各有16个变种:
// 注意 in 关键字,表示泛型是参数的类型约束
public delegate TResult Func(T arg);
public delegate TResult Func(T1 arg1, T2 arg2);
……
public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
//
//
public delegate void Action(T obj);
public delegate void Action(T1 arg1, T2 arg2);
……
public delegate void Action(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16);
依次表示一个参数、两个参数、……十六个参数 的方法。当然,你还可以写更多的参数,但是如果一个方法的参数超过10个,为什么不用类封装起来呢?即使不封装,一个方法十几个参数,你确定不会被你的领导嫌弃吗。
言归正传,介绍完了Func
和Action
的定义,那么如果使用呢?
public void Demo1()
{
// 一个没有返回值,也没有参数的方法
}
Action act1 = Demo;// 直接给一个方法名
public void Demo2(string name)
{
//有一个参数,但没有返回值的方法
}
Action act2 = Demo2;
public String Demo3()
{
// 有返回值,但没有参数的方法
}
Func func1 = Demo3;
public int Demo4(double data)
{
// 返回值是int,有一个参数是double的方法
}
Func func2 = Demo4;
以上是通过方法名获取Func
和Action
的方法,下面介绍一下通过Lambda表达式的方式创建Func
和Action
:
Action act1 = ()=> // lambda 的标志性 声明方式 =>
{
// 这是一个没有返回值,也没有参数的 lambda表达式
};
Action act2 = (age) =>
{
// 这是一个 参数为int ,没有返回值的 lambda表达式
};
//=========================================
Func func1 = () => ""; // 这是一个返回了空字符串的lambda表达式,注意这种写法
Func func2 = () =>
{
return ""; //与上一个等价
}
Func func3 = (age) =>
{
return "我的年纪是:"+age;// 一个参数是int,返回类型是string的lambda表达式
}
在lambda表达式中,当使用的是有返回值的方法体时,如果方法体是个简单的计算式或者说可以在一行内写完(或被编译器认为是一行)的话,可以省略 {
、}
和return
,直接用 =>
标记。
比如说以下内容:
Func cal_area = (width, height) => width * height;// 计算面积
现在我们手里有一大堆的Action
和Func
,我们该怎么用呢?
有以下两种常见的用法:
// 上接上文代码
act1();// 执行 act1 代表的方法或lambda表达式
act2(10); //执行act2 的lambda表达式
string str1 = func1();
string str2 = func3(10);
int area = cal_area(29,39);
act1.Invoke();
act2.Invoke(10);
area = cal_area.Invoke(33,63);
看过反射篇的应该对Invoke有一定印象,这个与MethodInfo里的Invoke类似,但是比其更加简单。
正如前言所述,Linq是一种对集合、数据源的集成式查询方式,它是对IEnumerable
的扩展方法集,所以想要使用Linq的话,需要引用两个命名空间 System.Linq
和System.Linq.Expressions
。
Linq有两种使用方式,一种是通过方法链的方式调用,一种是类似SQL语句的方式进行数据查询。方法链是基础,类SQL方式是语法糖。下面简单介绍一下两种方式的使用,不过首先先假设我们有一个数据很多的集合:
IEnumerable scores = new List();//假设存放了某班50个人的语文成绩
csharp
IEnumerable result1 = scores.Where(t => t > 60);
csharp
int count = scores.Count(t => t >= 60);
csharp
int sum = scores.Sum();
csharp
IEnumerable result2 = scores.Select(t => t % 10);
查询所有大于等于60的分数:
IEnumerable result3 = from score in scores
where score >= 60
select score;
简单介绍一下,类SQL形式有一个统一的格式写法,关键字from
、in
、select
缺一不可:
from 临时变量名 in 数据源
select 结果类型
where 是条件过滤,如果查询全部,可以忽略。
这种方式之所以被我称为是类SQL形式,是因为它的写法和SQL及其相似,熟悉SQL的可以很快上手。
因为SQL形式的查询里每一个关键字背后都有一个方法作为支撑,除了from 和in。
select 对应的Select 方法,where对应的Where方法。
需要特别注意的一点:
Linq查询是一种延迟查询,也就是说当返回类型是一个IEnumerable 的时候不会立即返回结果,必须调用ToList
才能获取到实际查询结果。另外需要注意的是,ToList
返回的是一个不可变List集合
Predicate
谓词、断言,等价于 Func
即返回bool的表达式Expression
表达式树,这个类很关键,但是在这里会细说,我们会讲它的一个特殊的泛型类型:Expression>
这个在某些数据源的查询中十分重要,它代表lambda表达式中一种特殊的表达式,即没有大括号和return
关键字的那种。我们先准备两个类:
///
/// 学生
///
public class Student
{
///
/// 学号
///
public int StudentId { get; set; }
///
/// 姓名
///
public string Name { get; set; }
///
/// 班级
///
public string Class { get; set; }
///
/// 年龄
///
public int Age { get; set; }
}
///
/// 科目
///
public class Subject
{
///
/// 名称
///
public string Name { get; set; }
///
/// 年级
///
public string Grade { get; set; }
///
/// 学号
///
public int StudentId { get; set; }
///
/// 成绩
///
public int Score { get; set; }
}
Subject 和Student通过学号字段一一关联,实际工作中数据表有可能会设计成这。
那么先虚拟两个数据源:IEnumerable
和 IEnumerable
。先忽略这两个数据源的实际来源,因为在开发过程中数据来源有很多种情况,有数据库查询出来的结果、远程接口返回的结果、文件读取的结果等等。不过最后都会整理成IEnumerable
的子接口或实现类的对象。
where的方法声明:
public IEnumerable Where (this IEnumerable source, Func predicate)
可以看出不会转换数据类型,通过给定的lambda表达式或者一个方法进行过滤,获取返回true的元素。
示例:
// 获取年纪大于10但不大于12的同学们
List results = students.Where(t=>t.Age >10 && t.Age<= 12).ToList();
注意在调用ToList之后数据才会实质上查询出来。
Group的方法声明有很多种:
最常用的一种是:
public static IEnumerable> GroupBy (this IEnumerable source, Func keySelector);
示例:
//将学生按照班级进行分组
List> list = students.GroupBy(p => p.Class).ToList();
它们是一对方法,一个是升序一个降序,其声明是一样的:
常用的是:
public static System.Linq.IOrderedEnumerable OrderBy (this IEnumerable source, Func keySelector);
示例:
//按年龄的升序排列:
List results = students.OrderBy(p => p.Age).ToList();
//按年龄的降序排列:
List results = students.OrderByDescending(p => p.Age).ToList();
这组方法有两个常用的重载声明:
First:
// 直接获取第一个
public static TSource First (this IEnumerable source);
// 获取满足条件的第一个
public static TSource First (this IEnumerable source, Func predicate);
Last:
// 直接获取最后一个
public static TSource Last (this IEnumerable source);
// 获取最后一个满足条件的元素
public static TSource Last (this IEnumerable source, Func predicate);
示例:
Student student = students.First();// 等价于 students[0];
Student student = students.First(p=>p.Class == "一班");//获取数据源中第一个一班的同学
Student student = students.Last();//最后一个学生
Student student = students.Last(p=>p.Class == "三班");//获取数据源中最后一个三班的同学
注意:
Any:是否存在元素满足条件
有两个版本,不过意思可能不太一样:
public static bool Any (this IEnumerable source);//数据源中是否有数据
//================
//是否存在满足条件的数据
public static bool Any (this IEnumerable source, Func predicate);
All :是否都满足条件:
public static bool Any (this IEnumerable source, Func predicate);
示例:
// 是否有学生
bool isAny = students.Any();
// 是否有五班的同学
bool isFive = students.Any(p=>p.Class == "五班");
// 是否所有学生的年纪都不小于9岁
bool isAll = students.All(p=>p.Age >= 9);
Skip一共有三个衍生方法:
第一个:Skip 自己: 略过几个元素,返回剩下的元素内容
public static IEnumerable Skip (this IEnumerable source, int count);
第二个:SkipLast,从尾巴开始略过几个元素,返回剩下的元素内容
public static IEnumerable SkipLast (this IEnumerable source, int count);
第三个:SkipWhile,跳过满足条件的元素,返回剩下的元素
public static IEnumerable SkipWhile (this IEnumerable source, Func predicate);
示例:
// 不保留前10个学生
List results = students.Skip(10).ToList();
// 不保留后10个学生
List results = students.SkipLast(10).ToList();
// 只要非一班的学生
List results = students.SkipWhile(p=>p.Class=="一班").ToList();
//上一行代码 等价于 = students.Where(p=>p.Class != "一班").ToList();
Take与Skip一样也有三个衍生方法,声明的参数类型也一样,这里就不对声明做介绍了,直接上示例。
//选取前10名同学
List results = students.Take(10).ToList();
// 选取最后10名同学
List results = students.TakeLast(10).ToList();
//选取 一班的学生
List results = students.TakeWhile(p=>p.Class=="一班").ToList();
// 上一行 等价于 = students.Where(p=>p.Class=="一班").ToList();
在使用Linq写分页的时候,就是联合使用Take和Skip这两个方法:
int pageSize = 10;//每页10条数据
int pageIndex = 1;//当前第一页
List results = students.Skip((pageIndex-1)*pageSize).Take(pageSize).ToList();
其中 pageIndex可以是任意大于0 的数字。Take和Skip比较有意思的地方就是,如果传入的数字比数据源的数据量大,根本不会爆粗,只会返回一个空数据源列表。
官方对于Select的解释是,将序列中的每个元素投影到新的表单里。我的理解就是,自己 定义一个数据源单个对象的转换器,然后按照自己的方式对数据进行处理,选择出一部分字段,转换一部分字段。
所以按我的理解,我没找到java8的同效果方法。(实际上java用的是map,所以没找到,:-D)
public static System.Collections.Generic.IEnumerable Select (this IEnumerable source, Func selector);
示例:
// 选出班级和姓名
List
Linq 里有几个需要注意的简单运算操作,这部分在使用中很常见。
Max获取数据源中最大的一个,不过只能是数字类型的,其他类型因为不能直接比较大小所以可以有替代方法,就是先排序取第一个。
以下是Max方法的两个重载版本:
public static int Max (this IEnumerable source);
public static int Max (this IEnumerable source,Func selector);
示例:
//查询学生中最大的年纪是多少
int maxAge = students.Select(t=>t.Age).Max();
方法类似与Max,不过与之不同的是获取最小的一个,不能应用于非数字类型。
示例:
// 查询学生中最小的年纪是多少
int minAge = students.Select(t=> t.Age).Min();
//=======
int minAge = students.Min(p=>p.Age);
与 Max/Min是一样类型的方法,依旧不能应用于非数字类型。
示例:
// 查询学生的评价年纪
int averageAge = students.Select(t=>t.Age).Average();
int averageAge = students.Average(p=>p.Age);
对数据源进行求和或者对数据源的某个字段进行求和,还是不能对非数字类型进行求和
示例:
// 一个没有实际意义的求和,学生的年龄总和
int sumAge = students.Select(t=>t.Age).Sum();
//
int sumAge = students.Sum(p=>p.Age);
判断数据源中是否包含某个元素,返回一个bool值,如果包含则返回true,如果不包含则返回false。该方法有两个重载版本,一个是使用默认的Equals
方法,一个是指定一个相等性比较器实现类。
public static bool Contains (this IEnumerable source, TSource value);
//传入相等性比较器的
public static bool Contains (this IEnumerable source, TSource value, IEqualityComparer comparer);
值得注意的是,这里的相等比较器是一个接口,也就是说需要使用类来实现这个方法。通常在实际开发过程中,我们会在TSource这个数据源所代表的类上增加 IEqualityCompare的实现。
示例1:
Student student1 = new Student();// 初始化一个学生类
Student student2 = students.First();// 从数据源中取一个
bool isContains = students.Contains(student1);// 返回 false,
bool isContains2 = students.Contains(student2);// 返回 true
说明: 类的默认相等比较是比较是否是同一个对象,即返回的
示例2:
创建一个相等性比较器,值得注意的是,相等性比较器有两个方法,一个是比较元素是否相等,一个是返回元素的HashCode,这两个方法必须在判断元素是否相等上保持结果一致。
public class StudentEqualityCompare: IEqualityComparer
{
public bool Equals(Student x, Student y)
{
// 省略逻辑
}
public int GetHashCode(Student obj)
{
//省略逻辑
}
}
使用:
StudentEqualityCompare compare = new StudentEqualityCompare();
Student student = students.First();
bool isContains = students.Contains(student, compare);
这是一组行为一样的方法,就是对数据源进行计数,不同的是Count返回int,LongCount返回long。
它们的声明有以下两种,这里选了Count的声明:
public static int Count (this IEnumerable source);
public static int Count (this IEnumerable source, Func predicate);
示例:
int count = students.Count();//返回一共有多少个学生
int count = students.Count(p=>p.Class=="一班");// 统计一班一共有多少学生
之前介绍了单个数据源的操作方法,这些方法不会让数据源发生变化,更多的对数据源进行过滤和选择或者统计。现在介绍几个对多个数据源进行操作的方法。
联合另一个数据源,意思就是把两个数据源合并到一个里面,去掉重复的元素,只保留不重复的元素,并返回这个结果集。
与Contains方法差不多,这个方法有两个重载的版本:
public static IEnumerable Union (this IEnumerable first, IEnumerable second);
public static IEnumerable Union (this IEnumerable first, IEnumerable second, IEqualityComparer comparer);
示例:
先假设一个业务场景:
学校举办运动会,现在教务处收到了田径组 500米跑的报名名单和跳远的报名名单,需要看看一共有哪些学生报名了这两项赛事。
// 省略数据源,田径组的名单
IEnumerable students1 = new List();
//省略数据源来源,跳远组的名单
IEnumerable students2 = new List();
List all = students1.Union(student2).ToList();
这时候简单统计了一下所有人,但是后来教务处在核对的时候,发现有的人名重复了,需要判断是否是一个人,这时候就必须创建一个相等比较器了。
List all = students1.Union(student2,compare).ToList();
// 省略compare的实现,具体可参照Contains的比较器
获取同时存在于两个集合中的元素,与Union类似。
方法的声明如下:
public static IEnumerable Intersect (this IEnumerable first, IEnumerable second);
public static IEnumerable Intersect (this IEnumerable first, IEnumerable second, IEqualityComparer comparer);
示例:
继续之前的业务场景,现在教务处需要知道有哪些同学同时报名了两个比赛
List students = students1.Intersect(students2).ToList();
获取只存在于第一个集合的元素,从第一个集合中去除同时存在与第二个集合的元素,并返回。
方法的声明如下:
public static IEnumerable Except (this IEnumerable first, IEnumerable second);
public static IEnumerable Except (this IEnumerable first, IEnumerable second, IEqualityComparer comparer);
示例:
继续业务描述,教务处要一份只报名了500米的学生名单:
List students = students1.Except(students2).ToList();
数据源中的元素原本有一定的顺序,这个方法可以将数据源中的顺序翻转过来,原本是最后一个的变成了第一个
,第一个变成了最后一个。
简单示例:
char[] apple = { 'a', 'p', 'p', 'l', 'e' };
char[] reversed = apple.Reverse().ToArray();
对数据源进行去重,然后返回去重之后的结果。同样,这个方法有两个重载版本,一个有比较器,一个没有比较器。
// 不用比较器的
public static IEnumerable Distinct (this IEnumerable source);
// 设置比较器
public static IEnumerable Distinct (this IEnumerable source, IEqualityComparer comparer);
示例:
先描述一个可能会出现的场景,每个班级在各个赛事组提交报名信息的时候有点混乱,500米的负责老师把一个班的名单多录了一次,但是学生已经乱序了,现在需要把多录的去掉,也就是对数据进行去重。
List students = students1.Distinct();
之前的方法基本都是对一个类型的数据源进行操作,不会涉及其他类型的数据源。现在介绍一下怎么关联多个类型的数据源,类似于SQL里的多表链接查询。
按照一定的逻辑将两个数据源关联到一起,然后选择出需要的数据。
方法有这几个重载版本:
public static IEnumerable Join (this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector);
//
public static IEnumerable Join (this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer comparer);
这个方法的参数比较多,我们大概介绍一下这个方法的所有参数:
类型参数
参数
示例:
假设前天语文老师组织了一场考试,因为是模拟正式考试,所以答题纸上学生都只写了学号,现在需要把考试成绩和学生们联系在一起
List
基于键值等同性将两个序列的元素进行关联,并对结果进行分组。以上是官方介绍,我在开发过程中并没有使用过这个方法,不过这个方法完全可以认为是Join和Group的组合体,即先进行了一次Join然后又对数据进行一次分组。
方法声明:
// 使用默认比较器
public static IEnumerable GroupJoin (this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func,TResult> resultSelector);
//设置比较器
public static IEnumerable GroupJoin (this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func,TResult> resultSelector, IEqualityComparer comparer);
类型参数
参数
以下是官方给的示例:
class Person
{
public string Name { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public static void GroupJoinEx1()
{
Person magnus = new Person { Name = "Hedlund, Magnus" };
Person terry = new Person { Name = "Adams, Terry" };
Person charlotte = new Person { Name = "Weiss, Charlotte" };
Pet barley = new Pet { Name = "Barley", Owner = terry };
Pet boots = new Pet { Name = "Boots", Owner = terry };
Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
Pet daisy = new Pet { Name = "Daisy", Owner = magnus };
List people = new List { magnus, terry, charlotte };
List pets = new List { barley, boots, whiskers, daisy };
// Create a list where each element is an anonymous
// type that contains a person's name and
// a collection of names of the pets they own.
var query =
people.GroupJoin(pets,
person => person,
pet => pet.Owner,
(person, petCollection) =>
new
{
OwnerName = person.Name,
Pets = petCollection.Select(pet => pet.Name)
});
foreach (var obj in query)
{
// Output the owner's name.
Console.WriteLine("{0}:", obj.OwnerName);
// Output each of the owner's pet's names.
foreach (string pet in obj.Pets)
{
Console.WriteLine(" {0}", pet);
}
}
}
/*
This code produces the following output:
Hedlund, Magnus:
Daisy
Adams, Terry:
Barley
Boots
Weiss, Charlotte:
Whiskers
*/
因为这篇内容会涉及到多个数据源,所以这里需要准备一些类和数据,以下数据纯属虚构,不涉及到现实。
///
/// 学生
///
public class Student
{
///
/// 学号
///
public long StudentId { get; set; }
///
/// 姓名
///
public string Name { get; set; }
///
/// 年龄
///
public int Age { get; set; }
///
/// 班级
///
public string Class { get; set; }
}
///
/// 科目
///
public class Subject
{
///
///
///
public long SubjectId { get; set; }
///
/// 名称
///
public string Name { get; set; }
///
/// 年级
///
public string Grade { get; set; }
///
/// 教师
///
public string Teacher { get; set; }
}
///
/// 考试
///
public class Exam
{
///
/// 考试编号
///
public long ExamId { get; set; }
///
/// 科目编号
///
public long SubjectId { get; set; }
///
/// 学生编号
///
public long StudentId { get; set; }
///
/// 分数
///
public double Score { get; set; }
///
/// 考试时间:年-月 如202004 表示2020年04月
///
public int Time { get; set; }
}
数据源:
List students = new List();// 学生列表,忽略数据来源
List subjects = new List();// 科目列表,忽略数据来源
List exams = new List();// 考试列表,忽略数据来源
预先介绍一个概念,C#中有一种类型叫做匿名类型。因为C#的要求是万物皆对象,对象皆有类,所以每一个对象或者数据都是有类型在背后支撑的。但是有时候会需要一些一次性的只读类型,这时候声明一个完整的类就有点得不偿失了。什么是一次性的只读类型呢,就是我们只关心它有哪些属性,不关心它有什么方法,同时这个类对应的对象只能在初始化的时候给属性赋值其他时候不能重新赋值,而且这个类型只在方法内部使用,在这个变量使用完成之后这个类型也失去了意义,这种类型就是我们所说的一次性的只读类型。
那么这种类型怎么声明呢?先不急,先再介绍一个关键字var
。这个关键字有什么特别的地点吗?var
表示隐式“类型”,意思就是用var
声明的变量其类型需要编译器自己结合上下文推断,也就是说使用者和声明者都知道这个变量的类型,但是没有明说。
那么为什么需要介绍var
呢?原因在于,var
是匿名对象的基础。因为匿名对象不能用object
声明变量,原因有两点,第一,变量声明为object之后,我们所需要的属性就无法使用了;第二,匿名类型的对象无法直接类型转换为object。所以,想要正常使用匿名类型,必须用var
。
下面简单演示一下匿名类型的声明和使用:
var obj = new
{
Name = "萧三",
Age = 20
};
// obj.Name 萧三
// obj.Age 20
这就是匿名类型,声明了一个有Name和Age属性的对象,这个对象我们知道它有哪些属性,但是不知道它的类型是什么。
在介绍完需要的知识后,将通过实际的情况来比较一下流式查询和查询表达式两种写法。
// 流式查询
var results = students.Where(t=>t.Class=="三年一班");
// 查询表达式
var results = from s in students
where s.Class == "三年一班"
select s;
这两种查询方式结构都是IEnumerable 。
// 流式查询
var results = students.Where(t=>t.Name.StartWith("张"));
// 查询表达式
var results = from s in students where s.Name.StartWith("张") select s;
// 流式查询
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了。
// 流式查询
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)};
查询表达式中没有统计查询的相关关键字,只能通过方法来获取,同时查询表达式返回的是一个集合,所以没法直接通过查询表达式进行求和、求平均等。
// 流式查询
var results = students.OrderByDescending(t => t.Age);
// 查询表达式
var results = from s in students orderby s.Age descending select s;
// 流式查询
var results = students.OrderBy(t => t.Age);
// 查询表达式
var results = from s in students orderby s.Age select s;
// 流式查询
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;
前一部分介绍了简单的查询,这一部分介绍联合多个数据源进行一系列的查询操作。
// 流式查询
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;
// 流式查询
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;
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()
};