本文来自: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 表达式》。