C#中Linq延迟执行问题
本文来自:http://msdn.microsoft.com/zh-cn/library/bb399393(v=vs.110).aspx
http://www.cnblogs.com/zhanglinfan/articles/1457068.html
http://hi.baidu.com/ccitofhxxxbenwq/item/b930bca05b49f57f6cd455c6
延迟加载与立即加载
查询某对象时,实际上您只检索请求的对象。 不会同时自动获取相关对象。 (有关更多信息,请参见跨关系查询。)您无法看到相关对象尚未加载这一事实,原因是尝试访问它们时将产生检索它们的请求。
例如,您可能需要查询一组特定的订单,然后偶而向特定客户发送电子邮件通知。 您最初不一定需要检索与每个订单有关的所有客户数据。 您可以使用延迟加载将额外信息的检索操作延迟到您确实需要检索它们时再进行。 请看下面的示例:
反过来也可能是可行的。 您的应用程序可能必须同时查看客户数据和订单数据。 您了解同时需要这两组数据。 您了解一旦获得结果,您的应用程序就需要每个客户的订单信息。 您不希望一个一个地提交对每个客户的订单的查询。 您真正想要的是将订单数据与客户信息一起检索出来。
Northwnd db = new Northwnd(@"c:\northwnd.mdf"); db.DeferredLoadingEnabled = false; IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust; foreach (Customer custObj in custQuery)
{
foreach (Order ordObj in custObj.Orders)
{
ProcessCustomerOrder(ordObj);
}
}
您还可以在查询中联接客户和订单,方法是构建叉积并将所有相关数据位作为一个大型投影检索出来。 但这些结果并非实体。 (有关更多信息,请参见 LINQ to SQL 对象模型)。 实体是具有标识且您可以修改的对象,而这些结果将是无法更改和持久化的投影。 更糟的是,您将检索到大量的冗余数据,因为在平展联接输出中,对于每个订单,每个客户将重复出现。
您真正需要的是同时检索相关对象的集合的方法。 此集合是关系图的精确剖面,因此您检索到的数据绝不会比您所需要的数据多或少。 为此,LINQ to SQL 提供了 DataLoadOptions,用以立即加载对象模型的某一区域。 方法包括:
LoadWith 方法,用于立即加载与主目标相关的数据。
AssociateWith 方法,用于筛选为特定关系检索到的对象。
一,关于Linq延迟执行问题
var result=from c in Products
where c.Price>500
select c;
foreach(Product p in result){
Console.WriteLine(p.Name);
}
foreach(Product p in result){
Console.WriteLine(p.Name);
}
因此,对于上述程序,两个foreach语句会导致查询被执行两次,这种行为被称作
Linq延迟执行。如果使用不当,会导致各种程序效率问题,比如大量的数据绑定如果做得是Linq延迟执行,程序效率将会大大降低。
改进Linq延迟执行
var result=from c in Products
where c.Price>500
select c;
var list=result.ToList<Product>();
foreach(Product p in result){
Console.WriteLine(p.Name)
}
foreach(Product p in result){
Console.WriteLine(p.Name)
}
通过调用ToList或者ToArray方法,可以直接执行Linq查询,将查询结果缓冲再list变量中。从而可以避免Linq延迟执行的效率问题。
关于Linq中对象引用相等的问题
var result=
from c in db.Categorys
from p in c.Products
where p.ProductId=="FI-01"
select p;
// 第一次查询
Product p1=null;// 映射数据库中的一行(主键为"FI-01")
foreach(var item in result)
{
p1=item;
Console.WriteLine(item.Name);
}
// 第二次查询
Product p2=null;// 映射数据库中的一行(主键为"FI-01")
foreach(var item in result)
{
p1=item;
Console.WriteLine(item.Name);
}
Console.WriteLine(p1==p2);// p1与p2引用相等。 p1==p2相当于//object.ReferenceEquals(p1,p2)
(转载)LINQ - 延迟执行机制分析
其实翻开表面的语法干扰,内部机制不过是 C# 2.0 就已经存在的东西。在《LINQ to Object 执行流程不完整分析》中我们就已经分析了所谓延迟执行的原因。
- 编译器会将 LINQ 表达式编译成委托,然后作为参数传递给相应的扩展方法。
- 扩展方法(如 Enumerable.Select)只是创建了一个实现了 IEnumerable<T> 接口的对象,该对象持有委托和数据源对象的引用。
- 在没有调用 IEnumerable<T>.MoveNext() 之前,委托并不会被执行,自然这个委托中的 "外部变量" 不会被修改(参考《C# 2.0 - Anonymous Methods》)。
- 当我们对这个 "IEnumerable<T> 对象" 进行操作时,必然会调用 MoveNext(),从而触发委托,进而影响到 "外部变量"。
你或许还有点迷糊,没关系,看所谓经典的例子。
代码1var num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };var i = 0;var q = from n in num select ++i;foreach (var n in q){ Console.WriteLine("n = {0}; i = {1}", n, i);}
输出
n = 1; i = 1
n = 2; i = 2
n = 3; i = 3
n = 4; i = 4
n = 5; i = 5
n = 6; i = 6
n = 7; i = 7
n = 8; i = 8
n = 9; i = 9
代码2var num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };var i = 0;var q = (from n in num select ++i).ToList();foreach (var n in q){ Console.WriteLine("n = {0}; i = {1}", n, i);}
输出
n = 1; i = 9
n = 2; i = 9
n = 3; i = 9
n = 4; i = 9
n = 5; i = 9
n = 6; i = 9
n = 7; i = 9
n = 8; i = 9
n = 9; i = 9
为什么加了 ToList() 后,会导致结果发生如此大的变化呢?
代码1反编译结果int[] num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int i = 0;IEnumerable<int> q = num.Select<int, int>(delegate (int n) { return ++i;});foreach (int n in q){ Console.WriteLine("n = {0}; i = {1}", n, i);}
对照这个反编译结果,我们很容易理解上面演示的输出结果。当扩展方法 Enumerable.Select() 执行完成后,返回了一个实现了 IEnumerable<int> 接口的对象,该对象内部持有委托的引用,而这个委托又持有外部变量 i 的引用。也就是说,这时候大家都牵了根绳,委托没被执行,也没谁去改变 i 的值 (i = 0)。而一旦开始执行 foreach 代码块,每次循环都会调用 IEnumerable<T>.MoveNext() 方法,该方法内部开始调用委托,委托每次执行都会导致 i 的值都被累加一次(++i),故输出结果是 1~9。
代码2反编译结果int[] num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int i = 0;List<int> q = num.Select<int, int>(delegate (int n) { return ++i;}).ToList<int>();foreach (int n in q){ Console.WriteLine("n = {0}; i = {1}", n, i);}
要想看明白,我们还得搞清楚 ToList 这个扩展方法做了些什么。
public static class Enumerable{ public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { return new List<TSource>(source); }}public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable{ public List(IEnumerable<T> collection) { ICollection<T> is2 = collection as ICollection<T>; if (is2 != null) { int count = is2.Count; this._items = new T[count]; is2.CopyTo(this._items, 0); this._size = count; } else { this._size = 0; this._items = new T[4]; using (IEnumerator<T> enumerator = collection.GetEnumerator()) { while (enumerator.MoveNext()) { this.Add(enumerator.Current); } } } }}
由于扩展方法 Enumerable.Select() 返回的对象仅实现了 IEnumerable<T>,因此导致 List<T> 构造方法中 else 语句块被执行。在我们开始 foreach 循环之前,IEnumerable<T>.MoveNext() 和委托就已经被 List<T>.ctor 遍历执行了一遍,那么也就是说调用 ToList<TSource>() 扩展方法以后,i 的值已经被累加到 9 了。再接下来进行 foreach 循环就有误导的嫌疑了,q 作为 List<int> 存储了 1 ~ 9,但没有任何代码再去修改变量 i (i = 9),循环的结果不过是 i 被显示了 q.Count 次而已,输出结果要不都是 9 那才见鬼了呢。
至此,你应该对 LINQ 的所谓延迟执行有个初步的概念了,那么我们看看别人是怎么说的。
------------以下文字摘自 Furture C# - 《Linq 入门系列 select篇》 评论部分---------------
小结:
Q:通过上面几个例子,我们该如何理解LINQ的查询何时执行呢?
A:LINQ的查询执行遵循以下原则:
1、一般情况下(除了下面第三条说的情况),LINQ都是延迟执行,原因:以DLINQ为例,越晚被执行,对业务逻辑的理解就越清晰,DLINQ查询对数据库的请求压力越小。编译器对LINQ查询优化可作的事情越多。
2、由于是延迟执行,也就是调用的时候才去执行。这样调用一次就被执行一次,这样就具备了重复执行的功能,参看之前的几个重复执行的例子。而这个重复执行是不需要再此书写一边查询语句的。
3、如果查询中我们对查询结果使用了 ToArray、ToList、ToDictionary 这些转换成集合的扩展方法。使用这时候出来的对象是一个独立的集合数组,而不是LINQ查询,所以这时候不会出现多次查询,而只是一次查询。
即:var q = from n in numbers select ++i ; 这样一条语句我们可以认为它记录的不是等号右边的结果,而是记录的等号右边的表达式。
而 var q = (from n in numbers select ++i).ToDictionary(k => k); 这样一条语句我们记录的是等号右边的计算结果,而不是表达式。
-------摘录结束-------------------
上面这段摘录中作者的说法基本没啥问题,但多少有些误导的嫌疑。作者并没有给出具体导致延迟执行的原因分析,仅仅通过输出结果来判断,似乎不够深入,也缺乏可靠的依据。而 "以DLINQ为例,越晚被执行,对业务逻辑的理解就越清晰,DLINQ查询对数据库的请求压力越小。编译器对LINQ查询优化可作的事情越多。" 让我觉得有点怪怪的…… 编译器将表达式 "拆解" 成一个或多个 Lambda Expression,动态组合到一起,传递到最终的 DataQuery:IQueryable<T> 对象中。只有我们执行相关操作,触发 IEnumerable<T>.GetEnumerator() 方法时才会执行 ADO.NET 操作,这就是所谓延时执行的过程。
好了,我写本文的目的是希望在面对这些模糊的技术概念时,我们应该去了解背后的实现机制。纵然不深入到每行代码,起码也得知道执行流程。而仅仅通过一些转载和输出结果做出结论,实在不利于我们学习和提高,也帮不了更多的初学者。
更详细的信息可参考 《LINQ to SQL 执行流程不完整分析 (Update)》、《动态创建 Lambda 表达式》。
C#中Linq延迟执行问题的更多相关文章
- js中的延迟执行和定时执行
在js中,延迟执行函数有两种,setTimeout和setInterval,用法如下: function testFunction(){Console.log('hovertree.com');} s ...
- Linq延迟执行
LINQ中大部分查询运算符都有一个非常重要的特性:延迟执行.这意味着,他们不是在查询创建的时候执行,而是在遍历的时候执行(换句话说,当enumerator的MoveNext方法被调用时).让我们考虑下 ...
- 浅谈iOS开发中方法延迟执行的几种方式
Method1. performSelector方法 Method2. NSTimer定时器 Method3. NSThread线程的sleep Method4. GCD 公用延迟执行方法 - (vo ...
- linq 延迟执行带来的困扰
有这样一个案例: var filteredResult = from f in orgFileList select f; ; i < WorkStatusFilters.ListWorkSta ...
- iOS开发中方法延迟执行的几种方式
概述 项目开发中经常会用到方法的延时调用,下面列举常用的几种实现方式: 1.performSelector 2.NSTimer 3.NSThread线程的sleep 4.GCD 1.performSe ...
- LINQ之路 6:延迟执行(Deferred Execution)
LINQ中大部分查询运算符都有一个非常重要的特性:延迟执行.这意味着,他们不是在查询创建的时候执行,而是在遍历的时候执行(换句话说,当enumerator的MoveNext方法被调用时).让我们考虑下 ...
- Linq基础知识之延迟执行
Linq中的绝大多数查询运算符都有延迟执行的特性,查询并不是在查询创建的时候执行,而是在遍历的时候执行,也就是在enumerator的MoveNext()方法被调用的时候执行,大说数Linq查询操作实 ...
- LINQ中的"延迟查询"特性【转】
本文转载自:LINQ中的"延迟查询"特性 详细了解“延迟查询”:C#学习笔记(八)—–LINQ查询之延迟执行 很多标准查询操作符的设计原型都是返回一个IEnumerable< ...
- LINQ 的查询执行何时是延迟执行,何时是立即执行,以及查询的复用
延迟执行的经典例子: 我们用 select ++i 就可以看到在foreach 时候,查询才被执行. public static void Linq99(){ int[] numbers = n ...
随机推荐
- Android消息机制不完全解析(上)
Handler和Message是Android开发者常用的两个API,我一直对于它的内部实现比较好奇,所以用空闲的时间,阅读了一下他们的源码. 相关的Java Class: androi ...
- JavaScript原型链与继承
最近学习了<Javascript高级程序设计>面向对象部分,结合书中的例子总结一下原型链和继承部分的内容. 创建对象 在Js当中没有类这个概念,当我们想要创建具有相同属性的对象的时候,有如 ...
- scrollTo和scrollTo.js
最近做一个项目前端要用到scrollTo和滚动视觉差.顺便把两个东西拿出来温习一下. HTML DOM里面定义了scrollTo方法,用法:scrollTo(xpos,ypos),把内容滚动到当前的指 ...
- 事关Animation Tree的工作随笔(一)
最近的业务上,又回到Animation Tree这块了. 众所周知的是Animation Tree这些概念已经提出很久了,但是使用有着AT支持的CE引擎的项目,却依然义无反顾地没有使用AT,而且,连某 ...
- tomcat使用说明
tomcat使用说明 1.tomcat的目录结构及说明: 2.发布WEB应用 1).将应用目录达成war包.(将html.jsp.images.WEB-INF目录和当前目录下的所有后缀名为jsp. ...
- 【枚举+数学】【HDU1271】整数对 难度:五颗星
整数对 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submis ...
- android XML解析之DOM解析方式
DOM 解析方式步骤: 第一步:首选需要获得DOM解析器工厂实例 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ...
- Log4j学习手记
注:今天难得清闲,学了下log4j,参考博文 http://www.cnblogs.com/pony/archive/2008/08/25/1275484.html ,文字部分基本上来自该博文,我只是 ...
- sql一些命令
1.创建表 create table tSId ( tSid int identity(1,1) primary key, tSName varchar(10) check(len(tSName)&g ...
- javascript 高级程序设计学习笔记(面向对象的程序设计)继承
ECMAScript中描述了原型链的概念,原型链是实现继承的主要方法. 实现原型链继承有一种基本模式 function SuperType () { this.property = true; } S ...