一,LINQ的使用复习
-
常用集合类的拓展方法
LINQ关键的功能是提供了集合类的拓展方法,所有实现了IEnumberable 接口的类都可以使用这些方法,这些方法不是IEnumberable,而是以拓展方法的形式存在与System.Linq命名空间的静态类中。
这里定义一个用于测试的类
Employee{
public int Id {get;set;}
public string Name {get;set;}
public int Age {get;set;}
public bool Gender {get;set;}
public double Salary {get;set;}
}
-
数据过滤筛选
-
Where方法是用于根据条件对数据进行过滤的。
-
IEnumberable<Employee> list1 = list.Where(e => e.Salary > 2500 && e.Age < 34); foreach(Employee e in list1) { Console.WriteLine(e); }
-
-
获取数据条数
-
Count()方法用于获取数据条数,它有两个重载方法,一个没有参数,另一个有Func<TSource,bool>predicate类型参数。没有参数的重载方法用于获取集合的数据条数,而有predicate参数的可以获取集合中符合predicate条件的数据条数。
-
int count1 = list.Count(e => e.Salary > 5000 || e.Age < 30); int count1 = list.Where(e => e.Salary > 5000 || e.Age < 30).Count();
-
-
判断是否至少有一条满足条件的数据
-
Any方法用于判断集合中是否至少有一条满足条件的数据,返回值为bool类型。Any方法同样有两个重载方法,一个没有参数的,一个是有Func<TSource,bool> predicate类型参数的。
-
bool b1 = list.Any(e => e.Salary > 8000) bool b2 = list.Where(e => e.Salary > 8000).Any()
-
-
获取一条数据
-
LINQ中有4组获取一条数据的方法,分别是Single、SingleOrDefault、First、FirstOrDefault,返回的都是符合条件的一条数据
-
同样,每组方法有两个重载方法,一个没有参数,一个有Func<TSource,bool> predicate类型参数的。
-
区别
Single
如果确定有且仅有一条满足要求的数据那么就用Single方法。如果没有满足条件的数据,或者满足条件的数据多于一条,Single方法就会抛出异常SingleOrDefault
:如果确认最多只有一条满足要求的数据,那么就用SingleOrDefault方法。如果没有满足条件的数据,SingleOrDefault方法就会返回类型的默认值,如果满足条件的数据多于一条,SingleOrDefault就会抛出异常First
: 如果满足条件的数据有一条或者多条,First方法就会返回第一条数据,如果没有满足条件的数据,First方法就会抛出异常FirstOrDefault
:如果满足条件的数据有一条或者多条,FirstOrDefault方法就会返回第一条数据,如果没有满足条件的数据,FirstOrDefault方法就会满足返回类型的默认值
-
代码演示
-
Employee e1 = list.Single(e => e.Id == 6) Console.WriteLine(e1); Employee? e2 = list.SinleOrDefault(e=>e.Id==9); if(e2 == null) Console.WriteLine("没有 Id == 9的数据"); else Console.WriteLine(e2); Employee e3 = list.First(e=>e.Age > 30); Console.WriteLine(e3); Employee? e4 = list.FirstOrDefault(e=>e.Id>9999); if(e4 == null) Console.WriteLine("没有大于30岁的数据"); else Console.WriteLine(e4);
-
-
-
排序
-
OrderBy方法可以对数据进行正向排序,而OrderByDescending方法则可以对数据进行逆向排序
-
Console.WriteLine("-------按照年龄正序排序----------") var orderedItem1 = list.OrderBy(e => e.Age); foreach(var item in orderedItem1) { Console.WriteLine(item); } Console.WriteLine("-------按照年龄逆序排序----------") var orderedItem2 = list.OrderByDescending(e => e.Age); foreach(var item in orderedItem2) { Console.WriteLine(item); }
-
-
限制结果集
-
限制结果集用来从集合中获取部分数据,其主要应用场景是分页查询,比如从第2条开始获取3条数。
-
Skip(n)方法用于跳过n条数据,Take(n)方法用于获取n条数据。
-
var orderedItem1 = list.Skip(2).Take(3); foreach(var item in orderedItem1) { Console.WriteLine(item); }
-
Skip、Take方法也可以单独使用
-
-
聚合函数
- Max,Min,Average,Sum,Count,这些方法也可以和Where,Skip,Take等方法一起使用
-
分组
-
GroupBy方法用来进行分组
-
IEnumberable<IGrouping<TKey,TSource>> GroupBy<TSource.TKey>(this IEnumberable<TSource> source,Func<TSource,TKey> keySelector())
-
-
GroupBy方法的参数keySelector是一个分组表达式,GroupBy方法的返回值为IGrouping<TKey,TSource>类型IEnumberable。IGrouping是一个继承自IEnumberable的接口,IGrouping中唯一的成员就是Key属性,表示这一组数据的数据项。
-
IEnumberable<IGrouping<int,Employee>> items = list.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}"); }
-
-
投影
-
可以对集合使用Select方法进行投影操作,通俗来说就是把集合中的每一项转换为另外一种类型,Select方法的参数就是转换的表达式
-
IEnumberable<int> ages = list.Select(e=> e.Age); Console.WriteLine(string.Join(",",ages))
-
-
集合转换
-
集合操作的拓展方法返回值大多是IEnumberable类型,但是有一些地方需要数组类型或者List类型的变量,我们可以用ToArray和ToList方法分别把IEnumberable转换为数组和List类型
-
Employee[] items = list.Where(e=>e.Salary > 3000 ).ToArray(); List<Employee> items = list.Where(e=>e.Salary > 3000 ).ToList();
-
二,EF Core的原理探究
1.IQueryable的延迟执行
- IQueryable只是代表一个“可以放到数据库服务器去执行的查询”,它没有立即执行,只是“可以被执行”而已。
- 对于IQueryable接口调用非终结方法的时候不会执行查询,而调用终结方法的时候则会立即执行查询。
- **终结方法:遍历、ToArray()、ToList()、Min()、Max()、Count()**等
- 非终结方法:GroupBy()、OrderBy()、Include()、Skip()、Take()等。
- 简单判断:一个方法的返回值类型如果是IQueryable类型,那么这个方法一般就是非终结方法,否则就是终结方法。
2.为什么要实现延迟执行
- 可以在实际执行之前,分步构建IQueryable。
- 比如:定义一个方法根据给定的天艇宁search Words>来查询匹配的书;如果searchAll参数是true,则书名或者作者名中含有给定的search Wordsi都匹配,否则只匹配书名;如果orderByPrice参数为true,则按照价格排序,否则就自然排序;upperPrice参数代表价格上限。
void QueryBooks(string searchWords,bool searchAll,boolorderByPrice,double upperPrice)
- 试着传递不同参数,查看生成的$QL的不同。
3.IQueryable复用的应用之分页查询
- 分页的实现
- 1、Skip(3).Take(8)最好显式指定排序规则
- 2、需要知道满足条件的数据的总条数:用IQueryable的复用LongCount和Count
- 3、页数:
long pageCount(long)Math.Ceiling(count * 1.0 / pageSize);
void OutputPage(int pageIndex,int pageSize)
{
using TestDbContext ctx = new TestDbContext();
//这里先把查询规则创建出来
IQueryable<Book> books =ctx.Books.Where(b => !b.Title.Contains("张三"));
//获取满足条件的数据总条数
long count =books.LongCount();
//使用count * 1.0 / pageSize 可以计算出数据总页数,考虑到有可能最后一页不满,使用Ceiling()取整
long pageCount = (long)Math.Ceiling(count * 1.0 / pageSize);
Console.WriteLine("页数:"+pageCount);
var pagedBooks = books.Skip((pageIndex - 1) * pageSize).Take(pageSize);
foreach(var b in pageBooks)
{
Console.WriteLine(b.Id + ","+b.Title);
}
}
这里的pageIndex
代表页码,pageSize
参数代表页大小。考虑到pageIndex
序号是从1开始的,因此我们要使用Skip
方法跳过(pageIndex-1)*pageSize
条数据,再最多获取pageSize条数据,就可以获得正确的分页数据了。