IQueryable和IEnumerable 快读《ASP.NET Core技术内幕与项目实战》EFCore2.5:集合查询原理揭秘

本节内容 , 涉及4.6(P116-P130) 。主要NuGet包:如前述章节
【IQueryable和IEnumerable 快读《ASP.NET Core技术内幕与项目实战》EFCore2.5:集合查询原理揭秘】一、LINQ和EFCore的集合查询扩展方法的区别
1、LINQ和EFCore中的集合查询扩展方法 , 虽然命名和使用完全一样 , 都两者定义在不同的命名空间下 , 是不同的方法 。PS:LINQ定义在System.Linq中 , EFCore定义在Microsoft.EntityFrameworkCore中
2、我们将集合操作的扩展方法 , 划分为两类:①非立即执行方法 , 如Where、OrderBy、Select、GroupBy、Skip、Take、Include等;②立即执行方法:如Min、Max、Count、Sum、ToArray、ToList<T>、foreach等 。
3、当执行非立即方法时 , LINQ返回IEnumerable集合 , EFCore返回IQueryable集合 。两者最大区别为:LINQ会立即在服务器内存中执行计算(客户端评估);而EFCore会延迟执行 , 只有当我们执行立即执行方法后 , EFCore才会将之前定义的所有非立即执行方法 , 整合为SQL抛到数据库执行(服务端评估) 。
4、利用EFCore中IQueryable的特点 , 我们就可以充分利用客户端评估和服务端评估 , 达到延迟执行、简化代码、复用代码、平衡性能等目的
//LINQ返回IEnumerablevar nums = new int[] { 1, 2, 3, 4 };var numsNew = nums.Where(n => n > 2);//EFCore返回IQueryableusing var ctx = new MyDbContext();var books= ctx.Book.Where(a => a.Id > 0);二、IQueryable的延迟执行案例
//利用IQueryable延迟执行 , 拼接复杂查询//定义一个复杂查询的方法 , 接受参数①关键词;②是否同时匹配书名和作者名;③是否按价格排序;④最高价格void QueryBooks(string searchWords, bool searchAll, bool orderByPrice, double upperPrice){using var ctx = new MyDbContext();//查询低于最高价var books = ctx.Books.Where(b => b.Price <= upperPrice);//同时匹配书名和作者if(searchAll){books = books.Where(b => b.Title.Contains(searchWords) || b.AuthorName.Contains(searchWords));}//只匹配书名else{books = books.Where(b =>b.Title.Contains(searchWords));}//按照价格排序if(orderByPrice){books = books.OrderBy(b => b.Price);}//立即执行方法 , 遍历foreach(var item in books){Console.WriteLine($"书名:{item.Title} , 作者:{item.AuthorName}");}}//调用方法QueryBooks("LINQ", true, true, 30);//查询书名或作者名 , 按价格排序QueryBooks("LINQ", false, false, 50); //只查询书名 , 不按价格排序三、复用IQueryable的案例
//获得一个IQueryable集合books , 并三次复用它var books = ctx.Books.Where(b => b.Price >=20);//使用books集合 , 执行一次立即查询Console.WriteLine(books.Count());//再次使用books集合 , 执行第二次立即查询Console.WriteLine(books.Max(b => b.Price));//第三次立即查询foreach (var item in books.Where(b => b.PubTime.Year > 2000)){Console.WriteLine(item.Title);}四、结合使用服务端评估和客户端评估的案例
//使用立即执行方法ToList , 执行SQL查询(服务端评估) , 将结果存到服务器的内存中var books = await ctx.Books.Take(100000).ToListAsync();//使用服务器内存中的集合books , 进行遍历查询 , 在服务器上执行(客户端评估)foreach (var item in books){Console.WriteLine(item.Title);}//由于遍历条数比较多 , 需要一定时间//如果在遍历过程中 , 我们关闭数据库服务器 , 程序仍然可以正常进行//说明遍历前 , 已经将数据下载到客户端//大多数情况下 , 我们应该复用IQueryable , 但在方法返回IQueryable , 或嵌套遍历不同的DbSet时 , 需要考虑特别注意//出错情况1:方法返回IQueryable//方法中返回IQueryable时 , 会销毁上下文//正确应该返回:return ctx.Books.Where(b => b.Id>5).ToList();IQueryable<Book> QueryBooks(){using var ctx = MyDbContext();return ctx.Books.Where(b => b.Id>5);}foreach(var item in QueryBooks()){Console.WriteLine(item.Title);}//出错情况2:嵌套遍历不同的DbSet//嵌套循环 , 导致两个DataReader执行 , 大多数数据库不允许多个DataReader同时执行var books = ctx.Books.Where(b => b.Id > 1);foreach(var item1 in books){Console.WriteLine(item1.Title);foreach(var item2 in ctx.Authors){Console.WriteLive(item2.Id);}}

经验总结扩展阅读