第十一节:Linq用法大全(二)
一. 排序(orderby )
1. 说明
用于对查询出来的语句进行排序,orderby 默认是升序的;降序则用 orderby xxx descending。如果是多条件排序,则在orderby后面写多个排序条件,用 逗号 隔开,如果哪个字段是要降序排列,则在它后面加descending。
2. 案例
(1).单条件升序和降序
例如:获取所有产品信息,并按照单价升序(降序)。
升序 var q = from p in db.Products orderby p.unitPirce select p; 降序 var q = from p in db.Products orderby p.unitPirce descending select p;
(2).多条件复合排序
例如:获取顾客信息,并依次按照城市名、顾客名进行升序排序。
var q = from c in db.Customers orderby c.City,c.Name select c;
例如:获取顾客信息,并依次按照城市名降序、顾客名升序排序。
var q = from c in db.Customers orderby c.City descending, c.Name select c;
例如:获取每一类产品中单价最高的产品信息,将产品按照类别ID进行升序排列。
var q = from p in db.Procucts group p by p.KindId into g orderby g.Key select new { g.Key, HighestProducts= from p2 in g where p2.unitPrice == g.Max(m=>m.unitPrice) select p2 };
二. 分组(group by)
(底部有几个案例)
1. 说明-基本用法
主要是用来对数据进行归类,可以按照类别进行输出,或者统计某个类别下的数量、最大值、最小值. 下面以一个简单的例子来说明。
例如:将产品按照类别编号进行分类,并输出。
var q = from p in db.Products group p by p.CategoryID into g select g;
分析:from p in db.Products 表示从表中将产品对象取出来。group p by p.CategoryID into g 表示对p按CategoryID字段归类。 其中g.Key就是CategoryID,g代表以CategoryID为索引的多个集合,如下图:
另外,其结果命名为g,一旦重新命名,p的作用域就结束了,所以,最后select时,只能select g。当然,也不必重新命名可以这样写:
//不推荐 var q = from p in db.Products group p by p.CategoryID;
如何对上述结果进行遍历呢?
var data = q.toList(); //这里不用延迟加载 foreach (var gp in data) { //1.输出分类依据 Console.WriteLine("类别编号为:{0}", gp.Key); //2. 再次遍历输出类别下的内容 foreach (var item in gp) { //do something Console.WriteLine($"产品名称:{item.pName},产品价格:{item.unitPrice}"); }
}
同样是上面的例子,也可以这样输出匿名类。
var q = from p in db.Products group p by p.CategoryID into g select new { CategoryID = g.Key, g };
分析:在这句LINQ语句中,有2个property:CategoryID和g。这个匿名类,其实质是对返回结果集重新进行了包装。把g的property封装成一个完整的分组。如下图所示:
如何遍历呢?
var data = q.toList(); //这里不用延迟加载 foreach (var gp in data) { //1.输出分类依据(这里 gp.CategoryID 等价于 gp.g.Key) Console.WriteLine("类别编号为:{0}", gp.CategoryID); //2. 再次遍历输出类别下的内容 foreach (var item in gp.g) { //do something Console.WriteLine($"产品名称:{item.pName},产品价格:{item.unitPrice}"); } }
2. 多列分组
例如:按产品的分类,又按供应商分类进行分组,并输出产品信息。
var categories = from p in db.Products group p by new { p.CategoryID, p.SupplierID } into g select new { g.Key, g };
分析:既按产品的分类,又按供应商分类。在by后面,new出来一个匿名类。这里,Key其实质是一个类的对象,Key包含两个Property:CategoryID、SupplierID。用g.Key.CategoryID可以遍历CategoryID的值。
补充一下多列分组的结果:
CategorID SupplierID
c01 s01
c01 s02
c01 s02
c02 s02
c02 s01
c02 s01
将会分为四组
(1). c01-s01: 1个产品
(2). c01-s02: 2个产品
(3). c02-s01: 2个产品
(4). c02-s02: 1个产品
3. 表达式分组
例如:将产品分为两类,单价大于10的一类,单价小于10的一类。
var categories = from p in db.Products group p by new { Criterion = p.UnitPrice > 10 } into g select g;
4. 其它场景(分组求最大值,最小值、平均值、计数、求和等)
//1.按照分类ID进行分组,并且求出每组中单价的最大值 var q = from p in db.Products group p by p.CategoryID into g select new { g.Key, MaxPrice = g.Max(p => p.UnitPrice) }; //2.按照分类ID进行分组,并且求出每组中单价的最小值 var q = from p in db.Products group p by p.CategoryID into g select new { g.Key, MinPrice = g.Min(p => p.UnitPrice) }; //3.按照分类ID进行分组,并且求出每组中单价的平均值 var q = from p in db.Products group p by p.CategoryID into g select new { g.Key, AveragePrice = g.Average(p => p.UnitPrice) }; //4. 求每组的单价总和 var q = from p in db.Products group p by p.CategoryID into g select new { g.Key, TotalPrice = g.Sum(p => p.UnitPrice) }; //5. 求每组的产品个数 var q = from p in db.Products group p by p.CategoryID into g select new { g.Key, NumProducts = g.Count() };
特别注意:
以上关于group by的代码在传统的EF调用,和EFCore 2.2版本都也可以使用,但是在EFCore3.0 3.1中都不能使用了,报错。通过查源码GitHub,确实也存在这个问题,下面给出几种临时的解决方案:
https://github.com/dotnet/efcore/issues/19894
https://github.com/dotnet/efcore/issues/19663
https://github.com/dotnet/efcore/issues?utf8=%E2%9C%93&q=groupby
临时解决方案1:在groupby前面加 AsEnumerable
var q2 = db.T_UserInfor.AsEnumerable().GroupBy(u => u.userSex).ToList(); //可以了
3.x版本测试(重点)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var db = new ypfContext(); 2 3 //报错 (Client side GroupBy is not supported.) 4 //var list1 = (from p in db.T_UserInfor 5 // group p by p.userSex into g 6 // select g).ToList(); 7 8 //报错 ((GroupByShaperExpression错误) 9 //var list2 = (from p in db.T_UserInfor 10 // group p by p.userSex into g 11 // select new 12 // { 13 // myKey = g.Key, 14 // g 15 // }).ToList(); 16 17 //报错 18 //var list3 = (from p in db.T_UserInfor 19 // group p by new { ISMore30 = p.userAge > 30 } into g 20 // select new 21 // { 22 // g.Key, 23 // g 24 // }).ToList(); 25 26 27 //可以 28 //var list3 = (from p in db.T_UserInfor 29 // group p by p.userSex into g 30 // select new 31 // { 32 // g.Key, 33 // myCount=g.Count(), 34 // myAgeMax=g.Max(p=>p.userAge), 35 // myAgeMin=g.Min(p=>p.userAge), 36 // myAgeSum=g.Sum(p=>p.userAge), 37 // myAgeAvg=g.Average(p=>p.userAge) 38 // }).ToList(); 39 //foreach (var item in list3) 40 //{ 41 // Console.WriteLine($"Key:{item.Key},myCount:{item.myCount},myAgeMax:{item.myAgeMax},myAgeMin:{item.myAgeMin},myAgeSum:{item.myAgeSum},myAgeAvg:{item.myAgeAvg}"); 42 //} 43 44 45 /******************************************下面是改造*****************************************************/ 46 47 //1.可以 48 //var list1 = (from p in db.T_UserInfor.AsEnumerable() 49 // group p by p.userSex into g 50 // select g).ToList(); 51 //foreach (var item in list1) 52 //{ 53 // Console.WriteLine($"分类为:{item.Key}"); 54 // Console.WriteLine($"详情为:"); 55 // foreach (var cItem in item) 56 // { 57 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 58 // } 59 //} 60 61 //2.可以 62 //var list2 = (from p in db.T_UserInfor.AsEnumerable() 63 // group p by p.userSex into g 64 // select new 65 // { 66 // myKey = g.Key, 67 // g 68 // }).ToList(); 69 //foreach (var item in list2) 70 //{ 71 // Console.WriteLine($"分类为:{item.myKey}"); 72 // Console.WriteLine($"详情为:"); 73 // foreach (var cItem in item.g) 74 // { 75 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 76 // } 77 //} 78 79 //3.可以 80 //var list3 = (from p in db.T_UserInfor.AsEnumerable() 81 // group p by new { ISMore30 = p.userAge > 30 } into g 82 // select new 83 // { 84 // g.Key, 85 // g 86 // }).ToList(); 87 //foreach (var item in list3) 88 //{ 89 // Console.WriteLine($"分类为:{item.Key}"); 90 // Console.WriteLine($"详情为:"); 91 // foreach (var cItem in item.g) 92 // { 93 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 94 // } 95 //}
2.x版本测试
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var db = new ypfContext(); 2 3 //可以 4 //var list1 = (from p in db.T_UserInfor 5 // group p by p.userSex into g 6 // select g).ToList(); 7 //foreach (var item in list1) 8 //{ 9 // Console.WriteLine($"分类为:{item.Key}"); 10 // Console.WriteLine($"详情为:"); 11 // foreach (var cItem in item) 12 // { 13 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 14 // } 15 //} 16 17 //可以 18 //var list2 = (from p in db.T_UserInfor 19 // group p by p.userSex into g 20 // select new 21 // { 22 // myKey = g.Key, 23 // g 24 // }).ToList(); 25 //foreach (var item in list2) 26 //{ 27 // Console.WriteLine($"分类为:{item.myKey}"); 28 // Console.WriteLine($"详情为:"); 29 // foreach (var cItem in item.g) 30 // { 31 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 32 // } 33 //} 34 35 //可以 36 //var list3 = (from p in db.T_UserInfor 37 // group p by new { ISMore30 = p.userAge > 30 } into g 38 // select new 39 // { 40 // g.Key, 41 // g 42 // }).ToList(); 43 //foreach (var item in list3) 44 //{ 45 // Console.WriteLine($"分类为:{item.Key}"); 46 // Console.WriteLine($"详情为:"); 47 // foreach (var cItem in item.g) 48 // { 49 // Console.WriteLine($"{cItem.id},{cItem.userName},{cItem.userSex},{cItem.userAge}"); 50 // } 51 //} 52 53 54 //可以 55 //var list4 = (from p in db.T_UserInfor 56 // group p by p.userSex into g 57 // select new 58 // { 59 // g.Key, 60 // myCount=g.Count(), 61 // myAgeMax=g.Max(p=>p.userAge), 62 // myAgeMin=g.Min(p=>p.userAge), 63 // myAgeSum=g.Sum(p=>p.userAge), 64 // myAgeAvg=g.Average(p=>p.userAge) 65 // }).ToList(); 66 //foreach (var item in list4) 67 //{ 68 // Console.WriteLine($"Key:{item.Key},myCount:{item.myCount},myAgeMax:{item.myAgeMax},myAgeMin:{item.myAgeMin},myAgeSum:{item.myAgeSum},myAgeAvg:{item.myAgeAvg}"); 69 //} 70 71 72 73 /******************************************无须改造了*****************************************************/
临时解决方案2:更换groupby的位置
5. 案例
(1). 数据
List<Employee> list = new();
list.Add(new Employee { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });
(2). 实操
//案例1: 根据年龄分组,获取每组人数、最高工资、平均工资
{
var groupData = list.GroupBy(x => x.Age);
foreach (var gp in groupData)
{
Console.WriteLine($"--------------下面是年龄为:{gp.Key}的组--------------");
Console.WriteLine($"人数为:{gp.Count()}");
Console.WriteLine($"最高工资为:{gp.Max(u => u.Salary)}");
Console.WriteLine($"平均工资为:{gp.Average(u => u.Salary)}");
}
}
//案例2:分别输出男女员工各自工资大约2000的数量,并列出其姓名
{
var groupData = list.GroupBy(x => x.Gender);
foreach (var gp in groupData)
{
string genderStr = gp.Key == true ? "男" : "女";
Console.WriteLine($"--------------下面是{genderStr}员工--------------");
var myData = gp.Where(u => u.Salary > 2000).ToList();
Console.WriteLine($"人数为:{myData.Count()}");
foreach (var cItem in myData)
{
Console.WriteLine(cItem.Name);
}
}
}
//案例3: 不同性别的人数、平均工资、最小年龄 (连贯写法)
{
var items = list.GroupBy(e => e.Gender).Select(g => new
{
Gender = g.Key ? "男" : "女",
Count = g.Count(),
AvgSalary = g.Average(e => e.Salary),
MinAge = g.Min(e => e.Age)
});
foreach (var item in items)
{
Console.WriteLine($"性别{item.Gender},人数{item.Count},平均工资{item.AvgSalary:F},最小年龄{item.MinAge}");
}
}
三. Skip、Take、SkipWhile、TakeWhile
1. Take/TakeWhile
(1). Take: 获取集合的前n个元素。
例如:将产品按照产品ID升序排列,并获取前10个元素。
var q =(from p in db.Products orderby p.ProductId select p) .Take(10).toList();
(2). TakeWhile:直到某一个条件成立就停止获取。
例如:将产品按照产品ID升序排列,并获取元素到productId="008"为止。
var q =(from p in db.Products orderby p.ProductId select p) .TakeWhile(p=>p.ProductId=="008").toList();
2. Skip/SkipWhile
(1). Skip: 跳过集合的前n个元素,求剩下的元素。
例如:将产品按照产品ID升序排列,并获取除了前3个外的后续所有产品信息。
var q =(from p in db.Products orderby p.ProductId select p) .Skip(3).toList();
(2). SkipWhile: 跳过集合的前n个元素,直到符合某个条件为止,求剩下的元素。
例如:将产品按照产品ID升序排列,并获取除了前n个外直到的productId=“007”后续所有产品信息。
var q =(from p in db.Products orderby p.ProductId select p) .SkipWhile(p=>p.ProductId=="007").toList();
PS:Skip和Take联合使用,组成分页案例。 公式: Skip((pageIndex-1)*pageSize).Take(pageSize)
四. Any/All/Contains
1. Any
说明:用于判断集合中是否有元素满足某一条件。
例如:返回没有订单的客户
var q = from c in db.Customers where !c.Orders.Any() select c;
对应的SQL语句
SELECT * FROM [dbo].[Customers] AS [t0] WHERE NOT (EXISTS( SELECT NULL AS [EMPTY] FROM [dbo].[Orders] AS [t1] WHERE [t1].[CustomerID] = [t0].[CustomerID] ))
2. All
说明:用于判断集合中所有元素是否都满足某一条件。
例如:返回所有订单都运往其所在城市的客户或未下订单的客户。
var q = from c in db.Customers where c.Orders.All(o => o.ShipCity == c.City) select c;
3. Contains
说明:用于查看包含关系、模糊查询等
类比:SqlMethods.Like(c.CustomerID,"C%") 或 var data = dbContext.T_UserInfor.Where(u => EF.Functions.Like(u.userName, "%pf%")).ToList();
(1). 集合中是否包含某一元素
例如:查找"AROUT", "BOLID" 和 "FISSA" 这三个客户的订单。 这里相当于SQL语句中的in。
string[] customerID_Set = new string[] { "AROUT", "BOLID", "FISSA" }; var q = ( from o in db.Orders where customerID_Set.Contains(o.CustomerID) select o).ToList();
(2). 集合中包含多个元素
例如:查找其所在城市为西雅图、伦敦、巴黎或温哥华的客户。
string[] cities = new string[] { "Seattle", "London", "Vancouver", "Paris" }; var q = db.Customers.Where(p=>cities.Contains(p.City)).ToList();
五. Concat/Union/Intersect/Except
1. Concat 连接
连接不同的集合,不会自动过滤相同项。多个集合中连接的字段的类型必须完全一致。
例如:求消费者和雇员的联系方式。
var q = ( from c in db.Customers select c.Phone ).Concat( from e in db.Employees select e.HomePhone );
PS:以上代码中的Phone和HomePhone的类型必须完全一样,这样才能进行连接,连接后仍旧是一个字段,最终使用的时候,要再括号一下,最后ToList();
例如 : 求消费者和雇员的姓名和电话。
var q = ( from c in db.Customers select new { Name = c.CompanyName, c.Phone } ).Concat( from e in db.Employees select new { Name = e.FirstName + " " + e.LastName, Phone = e.HomePhone } );
PS:Customers中的Name和Phone必须要和Employees中的Name和Phone类型完全相同。
2. Union 合并
连接多个不同的集合,自动过滤相同的选项,即求并集。
例如:求所有顾客和所有职员所在的国家。
var q =(from c in db.Customers select c.Country) .Union (from e in db.Employees select e.Country);
3. Intersect 相交
求多个几个集合之间的交集。
例如:求所有顾客和所有职员公共所在的国家。
var q =(from c in db.Customers select c.Country ) .Intersect (from e in db.Employees select e.Country);
4. Except 排除
排除相交项;延迟。即是从某集合中删除与另一个集合中相同的项。先遍历第一个集合,找出所有唯一的元素,然后再遍历第二个集合,返回第二个集合中所有未出现在前面所得元素集合中的元素。
例如:求职员单独所在的国家,即这些国家不在顾客所在的国家之内。
var q =(from c in db.Customers select c.Country ) .Except (from e in db.Employees select e.Country);
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。