不管是在ado.net EF或者是在其余的Linq使用中,咱们常常会碰到两个重要的静态类Enumerable、Queryable,他们在System.Linq命名空间下。那么这两个类是如何定义的,又是来作什么用的呢?特别是Queryable类,它和EF的延迟加载技术有什么联系呢?html
好,带着上面的问题开始咱们今天的学习。sql
首先介绍两个类的定义数据库
(1)Enumerable类,对继承了IEnumerable<T>接口的集合进行扩展;缓存
(2)Queryable类,针对继承了IQueryable<T>接口的集合进行扩展。app
在继续学习以前,咱们先来看一下EF中定义的实体集DbSet<T>ide
经过上面的截图咱们能够看到 DbSet<T>实现了IQueryable<T>、IEnumerable<T>接口。工具
与上面的两句话结合起来意思就是能够经过两个静态类对DbSet<T>进行扩展操做。其实查看两个类的源码能够知道,这两个类对实现了IQueryable<T>、IEnumerable<T>接口的集合进行了不少方法的扩展。学习
可能你还不知道如何进行扩展方法的定义以及操做,没事儿,请参考另一篇文章:C#扩展方法的理解测试
可是那么的扩展方法不都是咱们须要的,咱们在ado.net EF中最经常使用的就是扩展的Where方法。ui
两个类中Where扩展方法的定义分别以下
(1)Enumerable类
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate); public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
观察Where方法,能够看到第一个参数是实现了IEnumable接口的类,第二个参数是一个Func<T>委托类型
(2)Queryable类
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate); public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate);
观察Where方法,能够看到第一个参数是实现了IEnumable接口的类,第二个参数是一个Expresssion类型
很显然,两个类扩展的Where方法是不一样的,那具体有什么不一样呢?那么这种不一样又致使什么结果呢?
OK,带着疑问继续往下学习。
为了便于你们好学习,在这里,咱们编写一段代码,经过监视工具查看二者的区别。
先上代码
private void Form1_Load(object sender, EventArgs e) { using (DemoContext context = new DemoContext()) { var customer = context.cunstomer.Where(c => c.Name == "牡丹"); foreach (var item in customer) { MessageBox.Show(item.Id.ToString()); } } }
至于代码中的上下文定义以及实体集你们没必要纠结,咱们在这里要透过表象看本质。在上面的程序中添加断点,同时启动sql server profiler监视工具,运行程序。
执行过断点处所在的语句,观察监视工具仍是什么都没有。
咦,是否是出什么问题了呢?为何没有查询语句执行呢?真的是监视工具出问题了吗?
继续单步调试
咦,这个时候怎么出现sql查询语句了。很奇怪吧,这就是ado.net EF的延迟加载技术,这里面很重要的一部分就是经过IQueryable接口实现的(具体咱们放到最后再说)。
讲过了Queryable类的Where方法,接下来咱们再来看一下Enumable类的Where方法。
修改上面的代码以下所示
private void Form1_Load(object sender, EventArgs e) { using (DemoContext context = new DemoContext()) { var customer = context.cunstomer.Where(c => c.Name == "牡丹").AsEnumerable(); foreach (var item in customer) { MessageBox.Show(item.Id.ToString()); } } }
一样是打开监视工具,添加断点,运行程序
单步调试,继续运行
执行过断点所在的语句及执行了查询语句。
关于上面的两个测试总结以下。
(1)全部对于IEnumerable的过滤,排序等操做,都是在内存中发生的。也就是说数据已经从数据库中获取到了内存中,只是在内存中进行过滤和排序操做。
(2)全部对于IQueryable的过滤,排序等操做,只有在数据真正用到的时候才会到数据库中查询。这也是Linq的延迟加载核心所在。
那最后一个问题,IQueryable接口为什么那么特殊呢?
观察它的定义
// 摘要: // 提供对未指定数据类型的特定数据源的查询进行计算的功能。 public interface IQueryable : IEnumerable { // 摘要: // 获取在执行与 System.Linq.IQueryable 的此实例关联的表达式树时返回的元素的类型。 // // 返回结果: // 一个 System.Type,表示在执行与之关联的表达式树时返回的元素的类型。 Type ElementType { get; } // // 摘要: // 获取与 System.Linq.IQueryable 的实例关联的表达式树。 // // 返回结果: // 与 System.Linq.IQueryable 的此实例关联的 System.Linq.Expressions.Expression。 Expression Expression { get; } // // 摘要: // 获取与此数据源关联的查询提供程序。 // // 返回结果: // 与此数据源关联的 System.Linq.IQueryProvider。 IQueryProvider Provider { get; } }
该接口有三个特殊的属性,具体内容代码已经介绍了,那查询时具体又是如何执行呢?
答案是该接口会把查询表达式先缓存到表达式树中,只有当真正遍历发生的时候,才会由IQueryProvider解析表达式树,生成sql语句执行数据库查询操做。
哎呀,写到如今终于差很少快写完了。
上面介绍了两个接口的区别与联系,具体使用哪一种就看本身的项目需求了。
最后补充一下List.Where()方法,仍是以代码说明。
List<string> fruits = new List<string> { "apple", "passionfruit", "banana", "mango", "orange", "blueberry", "grape", "strawberry" }; IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6); foreach (string fruit in query) { Console.WriteLine(fruit);
查看List<T>的定义,以下图所示
它也是继承了IEnumerable接口,所以,他也不存在延迟加载。