EF入门 IQueryable和IEnumberable的区别

Posted 新西兰程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EF入门 IQueryable和IEnumberable的区别相关的知识,希望对你有一定的参考价值。

IEnumerable接口

公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代。也就是说:实现了此接口的object,就可以直接使用foreach遍历此object;

IQueryable 接口

它继承 IEnumerable 接口,而因为.net版本加入Linq和IQueryable后,使得IEnumerable不再那么单调,变得更加强大和丰富。

为了区别两个接口,我们通过一个实际的例子来解释一下。

编写如下代码:

static void Main(string[] args)
        {
            //创建数据库访问网关
            using (SchoolDBEntities schoolEntities = new SchoolDBEntities())
            {
                //查询的结果放入IQueryable接口的集合中
                IQueryable<T_Class> classesIQue = (from c in schoolEntities.T_Class
                                                   orderby c.ID
                                                     select c).Skip<T_Class>(3).Take<T_Class>(3);
                //注意这个AsEnumerable<T_Class>()在分页查询之前,先将其转换成IEnumerable类型
                IEnumerable<T_Class> classesIEnu = (from c in schoolEntities.T_Class
                                                    orderby c.ID   
                                                    select c).AsEnumerable<T_Class>().Skip<T_Class>(3).Take<T_Class>(3);
                //因为启用了延迟加载机制,所以下面调用一下,才会真正去读取数据库
                int i = 0;
                foreach (var c in classesIQue)
                {
                    i++;
                }
                Console.WriteLine(i);
                foreach (var c in classesIEnu)
                {
                    i++;
                }
                Console.WriteLine(i);
            }
            Console.WriteLine("OK");
            Console.ReadKey();
        }

注意红色代码部分,在用linq查询实体集合之前我先将其转换成  IEnumerable接口类型,看看最终执行的sql是怎样的。

第一种:直接返回 IQueryable类型的查询,如下图所示:

第二种:在用分页查询之前先将其转换成 IEnumerable实际执行的sql如下图所示:

 

总结

IQueryable接口与IEnumberable接口的区别:  IEnumerable<T> 泛型类在调用自己的SKip 和 Take 等扩展方法之前数据就已经加载在本地内存里了,而IQueryable<T> 是将Skip ,take 这些方法表达式翻译成T-SQL语句之后再向SQL服务器发送命令,它并不是把所有数据都加载到内存里来才进行条件过滤。

 

以下转载地址  http://www.cnblogs.com/hiteddy/archive/2011/10/01/Difference_among_IQueryable_IEnumeralb_IList_in_Entity_Framework.html

 

使用工具追踪EF生成的SQL

 

使用Entity Framework等ORM框架的时候,SQL对于使用者来说是透明的,往往很多人也不关心ORM所生成的SQL,然而系统出现性能问题的时候就必须关注生成的SQL以发现问题所在。

使用过Toplink的朋友知道很只要设置日志打印级别=FINE就可以配置使之生成的SQL在服务器中打印出来,Entiry Framework没有那么幸运,在以前要检测生成SQL的唯一方法是SQL Server Profiler,但使用起来并不方便,结果也不能自动保存到文件中。

Tracing and Caching Provider Wrappers for Entity Framework是Entity Framework Team新推出的开源SQL追踪和二级缓存的解决方案。原理是在负责执行具体SQL语句的data provider(SqlClient或者其他Client)之上插入了一层WrappingProvider,用于监控DbCommand.ExecuteReader(), ExecuteScalar() and ExecuteNonQuery(),将Sql命令输出到指定介质或者将查询结果缓存起来以重用。

使用方法很简单,下载源代码编译后将dll添加到项目中,新加一个类WrappedNorthWindEntities继承原有的Entities即可,详见源代码中的示例。

测试IQueryable, IEnumerable, IList的区别

下面我们使用EF Wrapper来监测Entify Framework中IQueryable, IEnumerable和IList所生成的SQL。

private static void TestIQueryable()
{
    using (var ctx = new WrappedNorthWindEntities())
    {
        IQueryable<Product> expression = ctx.Products.Take(5);
        IQueryable<Product> products = expression.Take(2);   // A  不执行SQL
        Console.WriteLine(products.Count());          // B SELECT COUNT(1) FROM ( SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] ))
        Console.WriteLine(products.Count());          // C  SELECT COUNT(1) FROM ( SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] ))
        foreach (Product p in products)             // D  SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] 
        {
            Console.WriteLine(p.ProductName);
        }
        foreach (Product p in products)              // E  SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products] )
        {
            Console.WriteLine(p.ProductName);
        }
    }
}
private static void TestIEnumerable()
{
    using (var ctx = new WrappedNorthWindEntities())
    {
        IEnumerable<Product> expression = ctx.Products.Take(5).AsEnumerable();
        IEnumerable<Product> products = expression.Take(2);  // A  不执行SQL
        Console.WriteLine(products.Count());          // B SELECT TOP (5) * FROM [dbo].[Products]
        Console.WriteLine(products.Count());          // C  SELECT TOP (5) * FROM [dbo].[Products]
        foreach (Product p in products)             // D  SELECT TOP (5) * FROM [dbo].[Products] 
        {
            Console.WriteLine(p.ProductName);
        }
        foreach (Product p in products)              // E  SELECT TOP (5) * FROM [dbo].[Products]
        {
            Console.WriteLine(p.ProductName);
        }
    }
}
private static void TestIList()
{
    using (var ctx = new WrappedNorthWindEntities())
    {
        var expression = ctx.Products.Take(5);
        IList<Product> products = expression.Take(2).ToList(); // A  SELECT TOP (2) * FROM ( SELECT TOP (5) * FROM [dbo].[Products]

        Console.WriteLine(products.Count());            // B 不执行SQL
        Console.WriteLine(products.Count());            // C  不执行SQL
        foreach (Product p in products)               // D  不执行SQL
        {
            Console.WriteLine(p.ProductName);
        }
        foreach (Product p in products)                // E  不执行SQL
        {
            Console.WriteLine(p.ProductName);
        }
    }
}

 

测试结果

  1. IQueryable和IEnumerable都是延时执行(Deferred Execution)的,而IList是即时执行(Eager Execution)
  2. IQueryable和IEnumerable在每次执行时都必须连接数据库读取,而IList读取一次后,以后各次都不需连接数据库。前两者很容易造成重复读取,性能低下,并且可能引发数据不一致性
  3. IQueryable和IEnumerable的区别:IEnumberalb使用的是LINQ to Object方式,它会将AsEnumerable()时对应的所有记录都先加载到内存,然后在此基础上再执行后来的Query。所以上述TestIEnumerable例子中执行的SQL是"select top(5) ...",然后在内存中选择前两条记录返回。

以下是一个IQueryable引发数据不一致性的例子:记录总数和记录详情两者本应一致,但由于IQueryable前后两次读取数据库,结果是现实有10条记录,却输出11条详情。

 

    IQueryable<Product> products = ctx.Products.All();
    //开始的时候数据库product表中有10条记录, count = 10
    int count = products.Count();
    Console.WriteLine("Count of products:"+count);  
        
    //此时另一进程添加一个产品进数据库
    //会重新读取数据库并输出11个产品名称
    foreach (Product p in products)       
    {
    Console.WriteLine(p.ProductName);    
    }

 

结论

基于性能和数据一致性这两点,我们使用IQueryable时必须谨慎,而在大多数情况下我们应使用IList。

  • 当你打算马上使用查询后的结果(比如循环作逻辑处理或者填充到一个table/grid中),并且你不介意该查询会即时执行,使用ToList()
  • 当你希望查询后的结果可以供调用者(Consummer)作后续查询(比如这是一个"GetAll"的方法),或者你希望该查询延时执行,使用AsQueryable()

 

以上是关于EF入门 IQueryable和IEnumberable的区别的主要内容,如果未能解决你的问题,请参考以下文章

EF 将多个表合并为一个 IQueryable

使用 EF 和 WebAPI,如何返回 ViewModel 并支持 IQueryable/OData? [复制]

如何改造基于非通用的 EF Core 代码以使用通用 IQueryable

无法将类型“IQueryable”隐式转换为 Generic.IList' 从 EF 上下文中检索记录列表作为列表

IQueryable 实体框架 POCO 映射

EF ICollection Vs List Vs IEnumerable Vs IQueryable