当前位置:

C#的IQueryable vs IEnumerable

访客 2024-04-24 874 0

概要

我们在应用开发中经常用到IQueryable和IEnumerable两个接口。如果这两个接口搞混,往往会对程序性能造成很大的影响。

本文以一个实例来区分这两个接口,说明在实际开发过程中,这两个接口如何使用,尤其是IQueryable和接口IEnumerable中的LINQ扩展方法的使用。

基本定义

IEnumerable
IEnumerable是一个接口,客户代码通过实现该接口的GetEnumerator方法,来返回一个具体的IEnumerator.。

在实际应用中,IEnumerable面向内存对象,它会将所有的数据载入到内存中,再进行各种操作。IEnumerable包含一系列操作内存对象的扩展方法。

IQueryable
IQueryable是一个接口,一般通过第三方的DataProvider来实现接口中的方法。例如EntityFramework。

在实际应用中,IQueryable面向DataProvider,为其创建查询的SQL语句。

实例说明

本文通过一个使用EntityFramework来查询农业银行在天津的分行数据,来说明两个接口在实际使用中的差别。

IEnumerable获取数据

关键代码如下:

using(varcontext=newBranchContext(connectionString)){IEnumerable<Branch>branches=context.Branches.Where(b=>b.Id>1);branches=branches.Take(2);foreach(Branchbranchinbranches){System.Console.WriteLine(branch.Name);}}

运行结果如下:

结果分析:

  1. SQL语句中只有Id>1一个过滤条件。
  2. 数据库查询后,所有的查询结果全部载入内存IEnumerable对象中。
  3. IEnumerable的Take方法在内存中过滤出前两个分行数据,并显示。

IQueryable获取数据

关键代码如下:

using(varcontext=newBranchContext(connectionString)){IQueryable<Branch>branches=context.Branches.Where(b=>b.Id>1);branches=branches.Take(2);foreach(Branchbranchinbranches){System.Console.WriteLine(branch.Name);}}

运行结果如下:

结果分析:

  1. SQL语句中既有Id>1一个过滤条件,又有top关键字过滤前两个查询结果。
  2. 第一条语句执行后,IQueryable只是为EntityFramework生成了查询语句,过滤条件是Id>1,并没有真正进行数据库查询。
  3. 第二条Take语句执行后,IQueryable为EntityFramework重新生成了查询语句,包括了top部分。
  4. Foreach语句触发了数据库查询,所以我们看到的SQL即包含Id>1的过滤条件,又包含了top语句。

结论

IEnumerable中的Linq查询方法,只是针对内存对象。IQueryable中的Linq查询方法,是帮助第三放DataProvider生成查询的SQL语句。

所以我们在做大数据表服务器端分页操作时候,一定要小心。我们必须确保所有的查询方法都是基于IQueryable的,最后都能反应到SQL语句上。避免由于不慎使用IEnumerable,将大数据表全部载入内存,再进行各种过滤操作的情况。那样做,会使服务器压力激增。

附录

完整C#代码

Branch.cs

usingSystem.ComponentModel.DataAnnotations;usingSystem.ComponentModel.DataAnnotations.Schema;namespaceIQueryableIEnumerable{[Table("t_branch")]publicclassBranch{[Required]publicintId{get;set;}[Required]publicstringName{get;set;}[Required,Column("Addr")]publicstringAddress{get;set;}1710140072publicbyte[]RowVersion{get;set;}}}

BranchContext.cs

usingSystem;usingMicrosoft.EntityFrameworkCore;usingMicrosoft.Extensions.Logging;namespaceIQueryableIEnumerable{publicclassBranchContext:DbContext{privatereadonlyILoggerFactoryloggerFactory=LoggerFactory.Create(ConventionForeignKeyExtensions=>ConventionForeignKeyExtensions.AddConsole());privatereadonlystringConnectionString;publicDbSet<Branch>Branches{get;set;}publicBranchContext(stringconnectionString){ConnectionString=connectionString;}protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){optionsBuilder.UseLoggerFactory(loggerFactory);optionsBuilder.UseSqlServer(ConnectionString);}}}

Main函数

usingSystem;usingMicrosoft.EntityFrameworkCore;usingSystem.Linq;usingSystem.Collections.Generic;namespaceIQueryableIEnumerable{classProgram{staticvoidMain(string[]args){varconnectionString=@"Server=PC-20210618NEZE\SQL2012EXPRESS;Database=BranchMngt;Trusted_Connection=True;MultipleActiveResultSets=true";using(varcontext=newBranchContext(connectionString)){IQueryable<Branch>branches=context.Branches.Where(b=>b.Id>1);branches=branches.Take(2);foreach(Branchbranchinbranches){System.Console.WriteLine(branch.Name);}}/*using(varcontext=newBranchContext(connectionString)){IEnumerable<Branch>branches=context.Branches.Where(b=>b.Id>1);branches=branches.Take(2);foreach(Branchbranchinbranches){System.Console.WriteLine(branch.Name);}}*/}}}

SQL建表语句和数据初始化

ifobject_id(N't_branch',N'U')isnotnulldroptablet_branchcreatetablet_branch(Idintprimarykeyidentity(1,1),Namenvarchar(50)notnull,Addrnvarchar(200)notnull,[RowVersion][timestamp]NULL)insertintot_branch(Name,Addr)values(N'天津丽江道支行',N'天津市西青区李七庄街凌口村悦雅花园9号楼底商一层'),(N'中国农业银行',N'天津市南开区凌宾路'),(N'天津宾水西道支行',N'天津市南开区宾水西道阳光公寓A座首层、二层'),(N'天津李七庄支行',N'天津市河西区纪庄子道4号'),(N'天津阳光支行',N'天津市南开区港宁西路27号'),(N'环湖中路支行',N'天津市河西区环湖中路10号(13路终点站)')

发表评论

  • 评论列表
还没有人评论,快来抢沙发吧~