LINQ
LINQ是.NET Core中提供的简化数据查询的技术。使用LINQ技术,可以用几行代码就实现复杂的数据查询。LINQ不仅可以对普通的.NET集合进行查询,而且在Entity Frameword Core中应用广泛,因此必须熟练掌握LINQ。
要想熟悉掌握LINQ就的熟悉掌握委托——>Lambda——>LINQ
委托的用法
委托类型规定了方法的返回值和参数的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| MyDelegate d1 = SayEnglish; string s1 = d1(3); Console.WriteLine(s1); d1 = SayChinese; string s2 = d1(10); Console.WriteLine(s2); static string SayEnglish(int age) { return $"Hello {age}"; } static string SayChinese(int age) { return $"你好 {age}"; } delegate string MyDelegate(int n);
|
委托类型MyDelegate的变量d1可以指向与MyDelegate类型相容的方法,然后我们就可以像调用方法一样调用委托类型的变量。调用委托变量的时候执行的就是变量指向的方法。
在.NET中定义了最多可达16个参数的泛型类型委托Action(无返回值)和Fnc(有返回值),因此一般我们不需要自定义委托类型,可以直接使用Action或者Func这两个委托类型。
委托变量不仅可以指向普通方法,也可以指向匿名方法
1 2 3 4 5 6
| Func<int,int,string> f1= delegate(int il, int i2) { return $"{il}+{i2}={il + i2}"; }; string s=f1(1,2); Console.WriteLine(s);
|
定义匿名方法也可以采用Lambda表达式的语法。
1 2 3 4
| Func<int,int,string> f1= (il, i2) => { return $"{il}+{i2}={il + i2}"; };
|
俩段代码中,使用Lambda表达式,去掉了delegate关键字,并且省略了参数的数据类型,因为编译器能根据委托类型推断出参数的类型,然后用=>作为定义方法体的关键字。
但这段代码还可以进一步简化这些代码。如果=>之后方法体只有一行代码,并且方法有返回值,那么还可以省略方法体的花括号及return关键字。
1
| Func<int,int,string> f1=(il,i2)=>$"{il}+{i2}={il + i2}";
|
方法没有返回值,方法体只有一个代码可:
1
| Action<int,string> a1=(age,name)=>Console.WriteLine($"年龄{age},姓名{name}");
|
如果只有一个方法只有一个参数可:
1
| Func<int,int> f1=il=>il*2;
|
Lambda表达式例子
1 2 3 4 5 6 7 8 9 10 11 12
| int[] arrays={2,8,29,19,12,13,99,89,105,108,81}; var nums2=MyWhere(arrays,n=>n>30); Console.WriteLine(string.Join(",",nums2)); var nums3 = MyWhere(arrays, n => n % 2 == 0); Console.WriteLine(string.Join(",",nums3)); static IEnumerable<int> MyWhere(IEnumerable<int> nums, Func<int, bool> filter) { foreach (int n in nums) { if (filter(n)) yield return n; } }
|
如果不用Lambda表达式,用匿名方法的写法:
1 2 3 4 5
| Func<int,bool> f1= delegate(int n) { return n > 30; }; var nums2=MyWhere(arrays,f1);
|
可见Lambda表达式让我们编写匿名方法更简单。
LINQ 常用扩展方法
LINQ中提供类似Where的扩展方法,简化数据处理。
Where方法
Where方法:每一项数据都会经过predicate的测试,如果针对一个元素,predicate执行的返回值为true,那么这个元素就会放到返回值中。
Where参数是一个lambda表达式格式的匿名方法,方法的参数e表示当前判断的元素对象。参数的名字不一定非要叫e,不过一般lambda表达式中的变量名长度都不长。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 }); IEnumerable<Employee> items1 = employees.Where(e => e.Age > 25); foreach (Employee e in items1) { Console.WriteLine(e); } class Employee { public long 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}"; } }
|
Count()方法:获取数据条数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 }); IEnumerable<Employee> items1 = employees.Where(e => e.Age > 25); foreach (Employee e in items1) { Console.WriteLine(e); }
Console.WriteLine(employees.Count()); Console.WriteLine(employees.Count(e=>e.Age>25&&e.Salary>50000));
class Employee { public long 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}"; } }
|
Any()方法:是否至少有一条数据
满足条件返回true,不满足返回false
1 2
| Console.WriteLine(employees.Any(e=>e.Salary>80000)) Console.WriteLine(employees.Any(e=>e.Salary<10000))
|
获取一条数据 LINQ中有4组获取一条数据的方法,分别是Single、SingleOrDefault、First和FirstOrDefault。
Single:如果确认有且只有一条满足要求的数据,那么就用Single方法。如果没有满足条件的数据,或条件的数据多于一条,Single方法就会抛出异常。
SingleOrDefault:如果确认最多只有一条满足要求的数据,那么就用SingleOrDefault方法。如果没有满足条件的数据,SingleOrDefault方法就会抛出异常。
First:如果满足条件的数据有一条或多条,First方法就会返回第一条数据;如果没有满足条件的数据,First方法就会抛出异常。
FirstOrDefault:如果满足条件的数据有一条或者多条,FirstOrDefault方法就会返回第一条数据;如果没有满足条件的数据,FirstOrDefault方法就会返回类型的默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
Employee e1 = employees.Single(e => e.Id == 6); Console.WriteLine(e1); Employee? e2 = employees.SingleOrDefault(e => e.Id == 9); if(e2==null) Console.WriteLine("没有Id==9的数据"); else Console.WriteLine(e2); Employee e3 = employees.First(e => e.Age > 24); Console.WriteLine(e3); Employee? e4 = employees.FirstOrDefault(e => e.Age > 30); if (e4 == null) Console.WriteLine("没有大于30岁的数据"); else Console.WriteLine(e4); Employee e5 = employees.First(e => e.Salary > 99999);
class Employee { public long 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}"; } }
|
排序
OrderBy方法可以对数据进行正向排序,而OrderByDescending方法则可以对数据继续逆向排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
Console.WriteLine("--------按照年龄正序排列---------"); var orderedItems1 = employees.OrderBy(e => e.Age); foreach (var item in orderedItems1) { Console.WriteLine(item); } Console.WriteLine("--------安照工资倒序排列---------"); var orderedIdems2 = employees.OrderByDescending(e => e.Salary); foreach (var item in orderedIdems2) { Console.WriteLine(item); }
class Employee { public long 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}"; } }
|
限制结果集
限制结果集用来从集合中获取部分数据,其主要应用场景是分页查询,比如从第二页开始获取3条数据。
Skip(n)方法用于跳过n条数据,Take(n)方法用于获取n条数据。
Skip、Take方法也可以单独使用,employees.Skip(2)用于跳过2条数据,直到获取最后一条数据,反之employees.Take(2)获取前2条数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
var orderedItems3 = employees.Skip(2); foreach (var item in orderedItems3) { Console.WriteLine(item); }
var orderdItems4 = employees.Take(3); foreach (var item in orderdItems4) { Console.WriteLine(item); }
var orderdItems5 = employees.Skip(2).Take(3); foreach (var item in orderdItems5) { Console.WriteLine(item); }
class Employee { public long 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}"; } }
|
聚合函数
在SQL中Max、Min、Avg、Sum、Count的聚合函数。LINQ中也有对应的方法,它们的名字分别是Max、Min、Average、Sum、Count,这些方法也可以和Where、Skip、Take等方法一起使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
int maxAge = employees.Max(e => e.Age); Console.WriteLine($"最大年龄:{maxAge}"); long minId = employees.Min(e => e.Id); Console.WriteLine($"最小Id:{minId}"); double avgSalary = employees.Average(e => e.Salary); Console.WriteLine($"平均工资:{avgSalary}"); double sumSalary = employees.Sum(e => e.Salary); Console.WriteLine($"工资总和:{sumSalary}"); int count = employees.Count(); Console.WriteLine($"总条数:{count}"); int minSalary2 = employees.Where(e => e.Age > 30).Min(e => e.Salary); Console.WriteLine($"大于30岁的人群中的最低工资:{minSalary2}");
class Employee { public long 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}"; } }
|
如果集合是int等值类型和集合,我们也可以使用没有参数的聚合函数。
1 2 3 4 5
| int[] scores = { 61, 90, 100, 99, 18, 22, 38, 66, 80, 93, 55, 50, 89 }; int minScore = scores.Min(); Console.WriteLine($"最低成绩:{minScore}"); double avgScore1 = scores.Where(i => i > 60).Average(); Console.WriteLine($"合格成绩中的平局水平:{avgSalary}");
|
分组
LINQ中支持类似于SQL中的GroupBy实现分组操作。GroupBy方法用来进行分组。
对序列中的元素进行分组。
1
| <IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
|
GroupBy方法的参数keySelector是分组条件表达式,GroupBy方法的返回值为IGrouping<TKey, TSource>类型的泛型IEnumerable。IGrouping是一个继承自IEnumerable的接口,IGrouping中唯一的成员就是Key属性,表示这一组的数据项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
IEnumerable<IGrouping<int, Employee>> items = employees.GroupBy(e => e.Age); foreach (IGrouping<int, Employee> item in items) { int age = item.Key; int count = item.Count(); int maxSalary = item.Max(e => e.Salary); double avgSalary = item.Average(e => e.Salary); Console.WriteLine($"年龄:{item.Key},人数:{count},最高工资:{maxSalary},平均工资:{avgSalary}"); }
class Employee { public long 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
| var items = employees.GroupBy(e => e.Gender); foreach (var item in items) { bool gender = item.Key; int count = item.Count(); double avgSalary = item.Average(e => e.Salary); int minAge = item.Min(e => e.Age); Console.WriteLine($"性别:{gender},人数:{count},平均值:{avgSalary:F},最下年龄:{minAge}"); }
|
由于分组条件表达式用的bool类型的Gender属性,intem.Key就是bool类型。
投影
可以对集合使用Select方法进行投影操作,通俗来说就是把集合中的每一项逐项转换为另外一种类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| List<Employee> employees = new List<Employee>(); employees.Add(new Employee { Id = 1, Name = "Tim", Age = 28, Gender = true, Salary = 10000 }); employees.Add(new Employee { Id = 2, Name = "Tim", Age = 18, Gender = false, Salary = 20000 }); employees.Add(new Employee { Id = 3, Name = "Tim", Age = 20, Gender = true, Salary = 30000 }); employees.Add(new Employee { Id = 4, Name = "Tim", Age = 22, Gender = true, Salary = 40000 }); employees.Add(new Employee { Id = 5, Name = "Tim", Age = 24, Gender = false, Salary = 50000 }); employees.Add(new Employee { Id = 6, Name = "Tim", Age = 26, Gender = true, Salary = 60000 }); employees.Add(new Employee { Id = 7, Name = "Tim", Age = 27, Gender = false, Salary = 70000 }); employees.Add(new Employee { Id = 8, Name = "Tim", Age = 19, Gender = true, Salary = 80000 });
IEnumerable<int> ages = employees.Select(e => e.Age); Console.WriteLine(String.Join(",",ages)); IEnumerable<string> names = employees.Select(e => e.Gender ? "男" : "女"); Console.WriteLine(string.Join(",",names));
class Employee { public long 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}"; } }
|
Select方法中使用匿名类型
使用Select方法从employees的每一项提取出Name、Age属性的值,并且把Gender转换为字符串,Select方法的返回值是一个匿名类型的IEnumerable类型,因此我们必须用var声明变量类型。
1 2 3 4 5 6 7 8
| var items = employees.Select(e => new {e.Name, e.Age, XingBie = e.Gender ? "男" : "女"}); foreach (var item in items) { string name = item.Name; int age = item.Age; string xingbie = item.XingBie; Console.WriteLine($"名字={name},年龄={age},性别={xingbie}"); }
|
集合转换
集合操作的扩展方法的返回值大部分都是IEnumnerable<T>类型,但是有一些地方需要数组类型或者List<T>类型的变量,我们可以用ToArray和ToList方法分别把IEnumerable<T>转换为数组类型和List<T>类型。
1 2
| Employee[] items1 = employees.Where(e => e.Salary > 30000).ToArray(); List<Employee> items2 = employees.Where(e => e.Salary > 3000).ToList();
|
LINQ扩展方法的链式调用
1 2 3 4 5 6
| var items = employees.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3) .Select(g => new { Age = g.Key, Count = g.Count(), AvgSalary = g.Average(e => e.Salary) }); foreach (var item in items) { Console.WriteLine($"年龄:{item.Age},人数:{item.Count},平均工资:{item.AvgSalary}"); }
|
查询语句
1 2
| var items1=employees.Where(e => e.Salary > 30000).OrderBy(e=>e.Age) .Select(e => new {e.Name, e.Age, Gender = e.Gender ? "男" : "女"});
|
使用查询语法:
1 2 3 4
| var items2 = from e in employees where e.Salary > 3000 orderby e.Age select new { e.Name, e.Age, Gender = e.Gender ? "男" : "女" };
|