• 深入理解IEnumerable和IQueryable两接口的区别


    from:http://blog.csdn.net/ydm19891101/article/details/50969323

    无论是在ado.net EF或者是在其他的Linq使用中,我们经常会碰到两个重要的静态类Enumerable、Queryable,他们在System.Linq命名空间下。那么这两个类是如何定义的,又是来做什么用的呢?特别是Queryable类,它和EF的延迟加载技术有什么联系呢?

    好,带着上面的问题开始我们今天的学习。

    首先介绍两个类的定义

    (1)Enumerable类,对继承了IEnumerable<T>接口的集合进行扩展;

    (2)Queryable类,针对继承了IQueryable<T>接口的集合进行扩展。

    在继续学习之前,我们先来看一下EF中定义的实体集DbSet<T>

    通过上面的截图我们可以看到 DbSet<T>实现了IQueryable<T>、IEnumerable<T>接口。

    与上面的两句话结合起来意思就是可以通过两个静态类对DbSet<T>进行扩展操作。其实查看两个类的源码可以知道,这两个类对实现了IQueryable<T>、IEnumerable<T>接口的集合进行了很多方法的扩展。

    可能你还不知道如何进行扩展方法的定义以及操作,没事儿,请参考另外一篇文章:C#扩展方法的理解

    但是那么的扩展方法不都是我们需要的,我们在ado.net EF中最常用的就是扩展的Where方法。

    两个类中Where扩展方法的定义分别如下

    (1)Enumerable类

    [csharp] view plain copy
     
    1. public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);  
    2. public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);  

    观察Where方法,可以看到第一个参数是实现了IEnumable接口的类,第二个参数是一个Func<T>委托类型

    (2)Queryable类

    [csharp] view plain copy
     
    1. public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);  
    2. public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate);  

    观察Where方法,可以看到第一个参数是实现了IEnumable接口的类,第二个参数是一个Expresssion类型
    很显然,两个类扩展的Where方法是不同的,那具体有什么不同呢?那么这种不同又导致什么结果呢?

    OK,带着疑问继续往下学习。

    为了便于大家好学习,在这里,我们编写一段代码,通过监视工具查看两者的区别。

    先上代码

    [csharp] view plain copy
     
    1. private void Form1_Load(object sender, EventArgs e)  
    2. {  
    3.     using (DemoContext context = new DemoContext())  
    4.     {  
    5.         var customer = context.cunstomer.Where(c => c.Name == "牡丹");  
    6.         foreach (var item in customer)  
    7.         {  
    8.             MessageBox.Show(item.Id.ToString());  
    9.         }  
    10.     }  
    11. }  

    至于代码中的上下文定义以及实体集大家不必纠结,我们在这里要透过表象看本质。在上面的程序中添加断点,同时启动sql server profiler监视工具,运行程序。

    程序会在断点处停下来,如下所示

    上面只是到点断点处,当然断点处的语句还有执行,继续单步执行。

    执行过断点处所在的语句,观察监视工具还是什么都没有。

    咦,是不是出什么问题了呢?为什么没有查询语句执行呢?真的是监视工具出问题了吗?

    继续单步调试

    咦,这个时候怎么出现sql查询语句了。很奇怪吧,这就是ado.net EF的延迟加载技术,这里面很重要的一部分就是通过IQueryable接口实现的(具体我们放到最后再说)。

    讲过了Queryable类的Where方法,接下来我们再来看一下Enumable类的Where方法。

    修改上面的代码如下所示

    [csharp] view plain copy
     
    1. private void Form1_Load(object sender, EventArgs e)  
    2. {  
    3.     using (DemoContext context = new DemoContext())  
    4.     {  
    5.         var customer = context.cunstomer.Where(c => c.Name == "牡丹").AsEnumerable();  
    6.         foreach (var item in customer)  
    7.         {  
    8.             MessageBox.Show(item.Id.ToString());  
    9.         }  
    10.     }  
    11. }  

    同样是打开监视工具,添加断点,运行程序

    单步调试,继续运行

    执行过断点所在的语句及执行了查询语句。

    关于上面的两个测试总结如下。

    (1)所有对于IEnumerable的过滤,排序等操作,都是在内存中发生的。也就是说数据已经从数据库中获取到了内存中,只是在内存中进行过滤和排序操作。

    (2)所有对于IQueryable的过滤,排序等操作,只有在数据真正用到的时候才会到数据库中查询。这也是Linq的延迟加载核心所在。

    那最后一个问题,IQueryable接口为何那么特殊呢?

    观察它的定义

    [csharp] view plain copy
     
    1. // 摘要:  
    2. //     提供对未指定数据类型的特定数据源的查询进行计算的功能。  
    3. public interface IQueryable : IEnumerable  
    4. {  
    5.     // 摘要:  
    6.     //     获取在执行与 System.Linq.IQueryable 的此实例关联的表达式树时返回的元素的类型。  
    7.     //  
    8.     // 返回结果:  
    9.     //     一个 System.Type,表示在执行与之关联的表达式树时返回的元素的类型。  
    10.     Type ElementType { get; }  
    11.     //  
    12.     // 摘要:  
    13.     //     获取与 System.Linq.IQueryable 的实例关联的表达式树。  
    14.     //  
    15.     // 返回结果:  
    16.     //     与 System.Linq.IQueryable 的此实例关联的 System.Linq.Expressions.Expression。  
    17.     Expression Expression { get; }  
    18.     //  
    19.     // 摘要:  
    20.     //     获取与此数据源关联的查询提供程序。  
    21.     //  
    22.     // 返回结果:  
    23.     //     与此数据源关联的 System.Linq.IQueryProvider。  
    24.     IQueryProvider Provider { get; }  
    25. }  

    该接口有三个特殊的属性,具体内容代码已经介绍了,那查询时具体又是如何执行呢?

    答案是该接口会把查询表达式先缓存到表达式树中,只有当真正遍历发生的时候,才会由IQueryProvider解析表达式树,生成sql语句执行数据库查询操作。

    哎呀,写到现在终于差不多快写完了。

    上面介绍了两个接口的区别与联系,具体使用哪种就看自己的项目需求了。

    最后补充一下List.Where()方法,还是以代码说明。

    [csharp] view plain copy
     
    1. List<string> fruits =  
    2.     new List<string> { "apple", "passionfruit", "banana", "mango",   
    3.                     "orange", "blueberry", "grape", "strawberry" };  
    4.   
    5. IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);  
    6.   
    7. foreach (string fruit in query)  
    8. {  
    9.     Console.WriteLine(fruit);  

    查看List<T>的定义,如下图所示



    它也是继承了IEnumerable接口,因此,他也不存在延迟加载。
    OK,到此,所有工作完成。
  • 相关阅读:
    win10安装mongodb教程及其失败解决方案
    基于TypeScript的NodeJs框架:NestJs开发博客API (node.js+nest.js)
    Webpack性能改造之CDN
    44道JavaScript送命题
    electron-vue学习手册
    万字长文带你深度解锁Webpack(进阶篇)
    4W字长文带你深度解锁Webpack系列(上)
    devexpress GalleryControl 获得选中的item并删除
    c# 任意角度旋转图片
    .net core3.1连接GBase数据库的步骤
  • 原文地址:https://www.cnblogs.com/liuqiyun/p/8507356.html
Copyright © 2020-2023  润新知