2.1 LINQ简介
.NET Core中的LINQ是用来简化数据查询到的技术,使用LINQ技术可以用简洁的语句实现复杂的数据查询。LINQ不仅可以对.NET集合进行查询,在EF Core框架中也广泛应用,因此LINQ是NET人的必会之术。
2.2 Lambda表达式
应用LINQ技术离不开Lambda表达式,Lambda表达式的作用:偷懒,对偷懒,用Lambda可以在实际编程中大大的减少代码量,在“偷懒”的同时,也给代码的阅读带来了很大的便捷性。
2.2.1 委托
提到Lambda表达式,又不得不谈到委托,嘿嘿有点套娃的意思,不急,一步一步来。
委托是一种指向方法的类型,可以理解为函数的指针。下面演示一下委托的基本用法。
public class Program
{
//声明委托
delegate string MyDelegate(int i);
static void Main(string[]args)
{
//定义委托变量并绑定方法
MyDelegate mDelegate = SayHello;
//调用委托
Console.WriteLine(mDelegate(666));
mDelegate = SayBye;
Console.WriteLine(mDelegate(999));
}
static string SayHello(int i)
{
return i+"Hello World!";
}
static string SayBye(int i)
{
return i + "Bye Bye!";
}
}
执行结果
除此之外,.NET中定义了多参数的泛型委托Action(无返回值)和Func(有返回值)关于委托的详细解析,可以翻阅微软官方文档委托 - Visual Basic | Microsoft Learn或查看本农自学委托笔记.NETCore委托,C#委托的用法-CSDN博客
2.2.2 匿名方法
委托变量不仅可以指向普通方法,也可以指向匿名方法:
Func<int, int, int> funcDelegate = delegate (int i1, int i2) {
return i1 + i2;
};
2.2.3 Lambda表达式
将2.2.2中的匿名方法可以用Lambda表达式改写:
Func<int, int, int> funcDelegate1 = ( i1, i2)=>
{
return i1 + i2;
};
与匿名方法不同的是,在Lambda表达式中省略了关键字delegate 和入参的数据类型,这里编译器通过委托的数据类型自动推断出方法i1和i2的数据类型,用=>作为定义方法体的关键字
2.2.3 Lambda表达式的其他形式
(1)如果一个方法体只有一行代码,则可以省略方法体的花括号,例如:
Func<int, int,int> funcDelegate2 = (i1, i2) => i1 + i2;
(2)如果一个方法体只有一个参数,那么参数列表的圆括号也可以省略,例如:
Func<int, int> funcDelegate3 = i1 => i1 *10;
(3)Lambda 表达式使用示例(原著示例)
static void Main(string[] args)
{
int[] arrays = {40,10,20,30,50,60,52,45 };
var returnResult= Select(arrays,n=>n>30);//筛选大于30的数
var returnResult1 = Select(arrays, n =>n%2==0);//筛选偶数
}
static IEnumerable<int> Select(IEnumerable<int> nums,Func<int ,bool> filter)
{
foreach (int i in nums)
{
if (filter(i)) yield return i;
}
}
如果用匿名方法:
Func<int ,bool> f1 = delegate (int n) {
return n > 30;
};
var returnResult2 = Select(arrays, f1);
由此可见Lambda表达式的优势。
2.3 LINQ常用集合类的扩展方法
LINQ(Language Integrated Query)是C#中的一种查询语言,允许对集合(如数组、列表等)进行数据操作。通过使用LINQ,可以使用类似于SQL的语法来查询和操作数据。
LINQ的关键功能是提供了集合类的扩展方法,所有实现了IEnumberable<T>接口的类都可以使用这些方法。这些方法以扩展方法的形式存在于System.Linq命名空间的静态类中。
在示例讲解前,准备一些初始数据,并添加到List<Employee>
public class Employee
{
/// <summary>
///
/// </summary>
/// <param name="id">主键</param>
/// <param name="Name">姓名</param>
/// <param name="age">年龄</param>
/// <param name="gender">性别</param>
/// <param name="salary">工资</param>
public Employee(int id, string Name, int age, bool gender, int salary)
{
this.ID = id;
this.Name = Name;
this.Age = age;
this.Gender = gender;
this.Salary = salary;
}
/// <summary>
/// 主键
/// </summary>
public int ID;
/// <summary>
/// 姓名
/// </summary>
public string Name;
/// <summary>
/// 年龄
/// </summary>
public int Age;
/// <summary>
/// true 男 false 女
/// </summary>
public bool Gender;
/// <summary>
/// 工资
/// </summary>
public int Salary;
}
List<Employee> employeeList = new List<Employee>();
employeeList.Add(new Employee(1, "张三", 52, true, 8000));
employeeList.Add(new Employee(2, "李四", 34, false, 6000));
employeeList.Add(new Employee(3, "王五", 22, true, 3200));
employeeList.Add(new Employee(4, "牛七", 65, false, 4500));
employeeList.Add(new Employee(5, "马八", 12, true, 800));
employeeList.Add(new Employee(6, "不是", 7, false, 15000));
employeeList.Add(new Employee(7, "所有", 88, true, 6300));
employeeList.Add(new Employee(8, "虫子", 99, false, 4000));
employeeList.Add(new Employee(9, "都能", 65, true, 10000));
employeeList.Add(new Employee(10, "变成", 32, false, 4600));
employeeList.Add(new Employee(11, "蝴蝶", 45, true, 5300));
employeeList.Add(new Employee(12, "因为", 76, false, 7050));
employeeList.Add(new Employee(13, "有的", 89, true, 13000));
employeeList.Add(new Employee(14, "是她", 55, false, 16000));
employeeList.Add(new Employee(15, "娘的", 48, true, 20000));
employeeList.Add(new Employee(16, "蛆蛆", 28, false, 18000));
2.3.1 数据过滤:Where方法
Where方法用于根据条件进行过滤数据,声明如下
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
该方法的predicate参数是一个参数为元素类型,返回值bool的委托。source集合的所有元素经过predicate条件的筛选,所有符合条件的元素会迭代成一个集合,作为方法的返回值。
下面示例一下用法:筛选出年龄大于30且工资大于10000的员工
IEnumerable<Employee> resultList = employeeList.Where(emp => emp.Age > 30 && emp.Salary > 10000);
foreach (var item in resultList)
{
Console.WriteLine($"{item.Name} {item.Age} {item.Salary} ");
}
执行结果
有的 89 13000
是她 55 16000
娘的 48 20000
2.3.2 Count获取数据条数
Count方法用于获取数据条数,有两个重载,一个是获取查询条件后的集合的数据条数,一个是直接获取条数。
直接获取:
int count= employeeList.Count();
条件获取:
//有两种写法
int whereCount = employeeList.Where(emp => emp.Age > 30 && emp.Salary > 10000).Count();
int whereCount1 = employeeList.Count(emp => emp.Age > 30 && emp.Salary > 10000);
2.3.3 Any方法
用于判断是否至少有一条满足条件的数据。有两个重载,一个有条件参数,另一个没有。
无参数:
bool reBool = employeeList.Any();
有参数:
//有两种写法
bool reBool1 = employeeList.Any(emp => emp.Age > 30 && emp.Salary > 10000);
bool reBool12 = employeeList.Where(emp => emp.Age > 30 && emp.Salary > 10000).Any();
总结:Any只要遇到一个符合条件的数据就停止检查后续数据并返回结果,而Count检查所有数据,因此判断数据的存在与否Any方法比Count方法高效。
2.3.4 获取一条数据
有四组方法:Single、SingleOrDefault、First、FirstOrDefault
Single:如果确认有且只有一条满足要求的数据,那么就用 Single 方法。如果没有满足条件的数据,或者满足条件的数据多于一条,Single 方法就会抛出异常。
SingleOrDefault:如果确认最多只有一条满足要求的数据,那么就用 SingleOrDefault方法。如果没有满足条件的数据,SingleOrDefault 方法就会返回类型的默认值。如果满足条件的数据多于一条,SingleOrDefault 方法就会抛出异常。
First:如果满足条件的数据有一条或者多条,First 方法就会返回第一条数据:如果没有满足条件的数据,First 方法就会抛出异常。
FirstOrDefault:如果满足条件的数据有一条或者多条,FirstOrDefault 方法就会返回第一条数据:如果没有满足条件的数据,FirstOrDefault 方法就会返回类型的默认值。
使用示例:
Employee e1 = employeeList.Single(e=>e.ID==5);
Console.WriteLine($"{e1.ID } {e1.Name} {e1.Age} {e1.Salary} ");
Employee e2 = employeeList.SingleOrDefault(e =>e.Age==6 );
if (e2 == null)
Console.WriteLine("不存在年龄为6的员工");
Employee e3 = employeeList.First(e => e.ID == 7);
Console.WriteLine($"{e3.ID} {e3.Name} {e3.Age} {e3.Salary} ");
Employee e4 = employeeList.FirstOrDefault(e => e.Age == 8);
if (e4 == null)
Console.WriteLine("不存在年龄为8的员工");
执行结果:
5 马八 12 800
不存在年龄为6的员工
7 所有 88 6300
不存在年龄为8的员工
2.3.5 排序OrderBy、OrderByDescending
OrderBy正向排序,将所有员工按照年龄排序:
IEnumerable<Employee> orderbyList = employeeList.OrderBy(e => e.Age);
foreach (Employee item in orderbyList)
{
Console.WriteLine($"{item.ID}\t{item.Name}\t{item.Age}\t{item.Salary}");
}
执行结果
6 不是 7 15000
5 马八 12 800
3 王五 22 3200
16 蛆蛆 28 18000
10 变成 32 4600
2 李四 34 6000
11 蝴蝶 45 5300
15 娘的 48 20000
1 张三 52 8000
14 是她 55 16000
4 牛七 65 4500
9 都能 65 10000
12 因为 76 7050
7 所有 88 6300
13 有的 89 13000
8 虫子 99 4000
OrderByDescending逆向排序,所有员工安装工资逆向排序
Console.WriteLine("逆向排序:");
IEnumerable<Employee> orderbyDesList = employeeList.OrderByDescending(e => e.Salary);
foreach (Employee item in orderbyDesList)
{
Console.WriteLine($"{item.ID}\t{item.Name}\t{item.Age}\t{item.Salary}");
}
执行结果:
逆向排序:
15 娘的 48 20000
16 蛆蛆 28 18000
14 是她 55 16000
6 不是 7 15000
13 有的 89 13000
9 都能 65 10000
1 张三 52 8000
12 因为 76 7050
7 所有 88 6300
2 李四 34 6000
11 蝴蝶 45 5300
10 变成 32 4600
4 牛七 65 4500
8 虫子 99 4000
3 王五 22 3200
5 马八 12 800
2.3.6 限制结果集
限制结果集合用于从集合中获取部分数据,主要应用场景是分页查询,有两个方法:Skip Take
举例1:从第4条数据开始,获取后面5条数据
Console.WriteLine("限制结果:");
IEnumerable<Employee> skipTakeesList = employeeList.Skip(4).Take(5);
foreach (Employee item in skipTakeesList)
{
Console.WriteLine($"{item.ID}\t{item.Name}\t{item.Age}\t{item.Salary}");
}
执行结果:
限制结果:
5 马八 12 800
6 不是 7 15000
7 所有 88 6300
8 虫子 99 4000
9 都能 65 10000
举例2:Skip和Take也可以单独使用,比如Skip可以从第n条数据开始获取后面所有数据,即跳过前n个数据,例如
Console.WriteLine("Skip限制结果:");
IEnumerable<Employee> skipList = employeeList.Skip(10);
foreach (Employee item in skipList)
{
Console.WriteLine($"{item.ID}\t{item.Name}\t{item.Age}\t{item.Salary}");
}
执行结果:
Skip限制结果:
11 蝴蝶 45 5300
12 因为 76 7050
13 有的 89 13000
14 是她 55 16000
15 娘的 48 20000
16 蛆蛆 28 18000
举例3:Take单独使用,获取前3条数据:
Console.WriteLine("Take限制结果:");
IEnumerable<Employee> takeList = employeeList.Take(3);
foreach (Employee item in takeList)
{
Console.WriteLine($"{item.ID}\t{item.Name}\t{item.Age}\t{item.Salary}");
}
执行结果:
Take限制结果:
1 张三 52 8000
2 李四 34 6000
3 王五 22 3200
2.3.7 聚合函数
如SQL语句那样,LINQ中也有聚合函数:Max、Min、Average、Sum和Count等,可以和Where、Skip、Take等方法一起使用。
Max:获取工资最高值
Console.WriteLine("Max:");
int maxSalary = employeeList.Max(e => e.Salary);
Console.WriteLine($"最高工资:{maxSalary}");
执行结果:
Max:
最高工资:20000
Min:获取年龄最小
Console.WriteLine("Min:");
int minAge = employeeList.Min (e => e.Age );
Console.WriteLine($"最小年龄:{minAge}");
执行结果:
Min:
最小年龄:7
Average:计算平均年龄:
Console.WriteLine("Average:");
double avgAge = employeeList.Average(e => e.Age);
Console.WriteLine($"平均年龄:{avgAge}");
执行结果:
Average:
平均年龄:51.0625
Sum:计算所有员工工资总和:
Console.WriteLine("Sum:");
int sumSalary = employeeList.Sum(e => e.Salary);
Console.WriteLine($"工资总和:{sumSalary}");
执行结果:
Sum:
工资总和:141750
Count,获取数据条数,前文已经示例过了这里不再赘述。
2.3.8 分组GroupBy
LINQ中用GroupBy方法进行分组,声明如下:
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
参数列表中keySelector是分组的条件表达式,返回值为IGrouping<TKey, TSource>,其中TKey表示这组数据的数据项,例如按照性别分组,这个参数就是‘男’或‘女’,IGrouping继承自IEnumerable,因此可以用MinMaxCount等方法进行组内聚合运算。
示例:根据性别进行分组,然后计算组内人数、平均年龄、平均工资等
Console.WriteLine("分组运算:");
IEnumerable<IGrouping<bool, Employee>> groupByList = employeeList.GroupBy(e => e.Gender);
foreach (var item in groupByList)
{
Func<bool, string> g = t =>
{
if (t == true)
{
return "男";
}
else
{
return "女";
}
};
Console.WriteLine($"性别为{g(item.Key)}的人数:\t{item.Count()}");
Console.WriteLine($"性别为{g(item.Key)}的平均工资:\t{item.Average(e => e.Salary)}");
Console.WriteLine($"性别为{g(item.Key)}的平均年龄:\t{item.Average(e => e.Age)}");
}
执行结果
分组运算:
性别为男的人数: 8
性别为男的平均工资: 8325
性别为男的平均年龄: 52.625
性别为女的人数: 8
性别为女的平均工资: 9393.75
性别为女的平均年龄: 49.5
2.3.9 投影Select
Select方法:把集合中的每一项转换为另一种类型
举例1:提取成员年龄数据列
Console.WriteLine("年龄数据选择:");
IEnumerable<int> Ages = employeeList.Select(e=>e.Age);
foreach (var item in Ages)
{
Console.Write($"{item} ");
}
执行结果
年龄数据选择:
52 34 22 65 12 7 88 99 65 32 45 76 89 55 48 28
举例2:选择性别数据列
Console.WriteLine("性别数据选择:");
IEnumerable<string > genders = employeeList.Select(e => e.Gender ? "男":"女");
foreach (var item in genders)
{
Console.Write($"{item} ");
}
执行结果:
性别数据选择:
男 女 男 女 男 女 男 女 男 女 男 女 男 女 男 女
2.3.10 集合转换ToArray ToList
LINQ的扩展方法大多都是返回IEnumerable<T>类型,可以根据编码需求将其转换成List<T>类型例如选取男性员工:
Console.WriteLine("选择男性员工:");
IEnumerable<Employee> wEmployee = employeeList.Where(e => e.Gender==true );
foreach (var item in wEmployee)
{
Console.WriteLine($"{item.ID } \t{item.Name} ");
}
执行结果:
选择男性员工:
1 张三
3 王五
5 马八
7 所有
9 都能
11 蝴蝶
13 有的
15 娘的
2.4 LINQ的另一种写法
LINQ的应用中,不仅可以用方法语法,也可以用“查询语法”进行编码,例如:
方法语法:
var funcResult = employeeList.Where(e=>e.Salary>10000).OrderBy(e=>e.Age).Select(e=>new { e.Name,e.Age, gen = e.Gender?"男":"女"});
查询语法:
var sqlResult = from e in employeeList
where e.Salary > 10000
orderby e.Age
select new { e.Name, e.Age, gen = e.Gender ? "男" : "女" };
总结:
“查询语法”看起来更新颖,而且比“方法语法”需要写的代码会少一些,但是在编写复杂的查询条件的时候,用“方法语法”编写的代码会更清晰。