C#中的IQueryable vs IEnumerable

概要

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

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

基本定义

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

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

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

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

实例说明

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

IEnumerable获取数据

关键代码如下:

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

运行结果如下:
在这里插入图片描述
结果分析:

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

IQueryable 获取数据

关键代码如下:

using (var context = new BranchContext(connectionString)){
    IQueryable<Branch> branches = context.Branches.Where( b => b.Id > 1);
    branches = branches.Take(2);
    foreach(Branch branch in branches){
        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查询方法,是帮助第三放Data Provider生成查询的SQL语句。

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

附录

完整C#代码

Branch.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IQueryableIEnumerable
{
    [Table("t_branch")]
    public class Branch{
        [Required]
        public int Id { get; set; }
         [Required]
        public string Name { get; set; }
         [Required, Column("Addr")]
        public string Address { get; set; } 
        [Timestamp]
        public byte[] RowVersion { get; set; }
    }
}

BranchContext.cs

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IQueryableIEnumerable
{
    public class BranchContext : DbContext {
        private readonly ILoggerFactory loggerFactory 
        = LoggerFactory.Create(ConventionForeignKeyExtensions => ConventionForeignKeyExtensions.AddConsole());
        private readonly string ConnectionString;
        public DbSet<Branch> Branches {get;set;}
        public BranchContext(string connectionString)
        {
            ConnectionString = connectionString;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseLoggerFactory(loggerFactory);
            optionsBuilder.UseSqlServer(ConnectionString);
        }
    }
}

Main 函数

using System;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Collections.Generic;
namespace IQueryableIEnumerable
{
    class Program
    {
        static void Main(string[] args)
        {
            var connectionString = @"Server=PC-20210618NEZESQL2012EXPRESS;Database=BranchMngt;Trusted_Connection=True;MultipleActiveResultSets=true";
            using (var context = new BranchContext(connectionString)){
                IQueryable<Branch> branches = context.Branches.Where( b => b.Id > 1);
                branches = branches.Take(2);
                foreach(Branch branch in branches){
                    System.Console.WriteLine(branch.Name);
                }
            } 
            /* using (var context = new BranchContext(connectionString)){
                IEnumerable<Branch> branches = context.Branches.Where( b => b.Id > 1);
                branches = branches.Take(2);
                foreach(Branch branch in branches){
                    System.Console.WriteLine(branch.Name);
                }
            } */
        }
    }
}

SQL建表语句和数据初始化

if object_id(N't_branch',N'U') is not null drop table t_branch
  create table t_branch (
	Id int primary key identity(1,1),
	Name nvarchar(50) not null,
	Addr nvarchar(200) not null,
   [RowVersion] [timestamp] NULL
  )

  insert into t_branch (Name,Addr) values
   (N'天津丽江道支行',N'天津市西青区李七庄街凌口村悦雅花园9号楼底商一层'),
   (N'中国农业银行',N'天津市南开区凌宾路'),
   (N'天津宾水西道支行',N'天津市南开区宾水西道阳光公寓A座首层、二层'),
   (N'天津李七庄支行',N'天津市河西区纪庄子道4号'),
   (N'天津阳光支行',N'天津市南开区港宁西路27号'),
   (N'环湖中路支行',N'天津市河西区环湖中路10号(13路终点站)')

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>