linq中的tolist()方法什么意思

来源:互联网 发布:翡翠台直播软件 编辑:程序博客网 时间:2024/05/01 16:37

这是一个延迟执行(Deferred excution)的问题。  当调用where时,实际得到的不是集合,而是运算表达式,直到ToList时,才成为对应的集合。




Linq转换操作之ToArray,ToList,ToDictionary源码分析

一:linq中的转换运算符

1. ToArray

我们经常用在linq查询上吧。 linq只能运用在IEnumerable这个集合类型上面。

源码分析: 我们发现如果该类型可以转化为ICollection,我们最后执行CopyTo方法

如果不能转换为ICollection,那么就执行foreach。


2. ToList

这个我们发现比ToArray更简单,如果说可以转换为ICollection,那么它直接使用了List的构造函数,在List的构造函数中,我们看到非常

简单。如果不能转为就执行foreach添加。


3. ToDictionary


var list = new List<int>() { 10, 20, 30 };

var query2 = from n in list
select new { num = n };

var myarr = query2.ToDictionary(i => i.num);

从编译器中我们应该可以看到,toDictionary之后,我们的key是一个int,value为一个匿名类型 'a

[10, 'a],[20,'a],[30,'a]


最终可以看到ToDictionary最后调用的是

foreach (TSource current in source)
{
dictionary.Add(keySelector(current), elementSelector(current));

}






AsQueryable()

先说说什么是 IQueryable

IQueryable 是当前的 data provider 返回的类型. 在本文的例子中都是 EF data provider for sql server, 返回的是一种数据查询结构, 用于生成 sql 语句.

lazy load 特性

以下是一段最常见的代码,

var products = db.Product.where(p => p.Type == "food").select(p => new { p.Id, p.Name, p.CreateTime});

.Select 返回类型为 IQueryable<T>, 语句执行后不会立刻查询数据库, 而是在迭代使用 products 时才会查数据库, 具有 lazy load 的特性, 按需查数据库可提高程序效率.

迭代时上面的代码生成的 sql 语句类似:

select Id, Name, CreateTime from Product where Type = 'food'

对 products 再次使用数据库查询操作, 运行时会把结果合并为1条 sql 语句, 如下,

var products = db.Product.where(p => p.Type == "food").select(p => new { p.Id, p.Name, p.CreateTime});var orderedProducts = products.OrderBy(p => p.CreateTime);

迭代时生成的 sql 语句类似:

select Id, Name, CreateTime from Product where Type = 'food' order by CreateTime

显式调用 AsQueryable()?

如果对没有继承 IQueryable 接口的类型使用 AsQueryable(), 会转换类型, 如下,

int[] array = new { 1, 2, 4, 5};var enumArray = array.AsQueryable();

因为 Array 只继承了 IEnumerable, 没有继承 IQueryable, 所以会发生类型转换. 这种情况下使用没有任何作用, 因为 IQueryable 没有声明任何新方法.

本人更多是在单元测试中使用 AsQueryable() 模拟 EF data provider 返回的数据.

其他有用的情况我暂时还没碰到, 请大家指教.

IQueryable的限制

此外 IQueryable 有诸多限制, 只支持数据库查询语法, 无法支持 Linq to object 的操作, 如以下2段代码会在运行时出错,

var products = db.Product                .Where(p => p.Type == "food")                .Select(p => new { p.Id, p.Name, p.CreateTime.Date});                // 如果改成 .Select(p => new { p.Id, p.Name, DbFunction.TruncateDate(p.CreateTime)})                // 就能正常运行.
var products = db.Product                .Where(p => p.Type == "food")                .Select(p => MyMethod(p));

.Select() 的返回类型为 IQueryable.
第1段代码, 我认为 IQueryable 还不够智能, 无法把 p.CreateTime.Date 转换为 sql 相关的 function, 而使用 DbFunctions 要求使用者了解同时熟悉 linq to entity 及 sql 的内置方法.
第2段代码, 生成 的 sql 无法返回 MyMethod 类型, 是可以理解的.

但是, 对代码加了 AsEnumerable() 后运行正常, 因为 IEnumerable 支持 Linq to object 的操作.

AsEnumerable()

同样支持 lazy load, 但不要滥用

同样是延迟查询, 与 AsQueryable() 区别是, 迭代时遇到 AsEnumerable() 会先进行 sql 查询, 所以对已查出来的结果当然能进行 Linq to object 操作.

但是, 千万不要为了方便而滥用 AsEnumerable(), 可能会严重消耗资源, 如下代码,

var products = db.Product.AsEnumerable()                .Select(p => new {p.Id, p.Name, p.CreateTime.Date});

上面的代码在查询时会把整个Product表的结果存放进内存, 然后进行 .Select 查询!!!

当我不熟悉 DbFunction 或者 要用自定义返回类型应该怎么办

正确的做法应该如下,

var yourOwnType = db.Product                .Select(p => new {p.Id, p.Name, p.CreateTime})                .AsEnumerable()                .Select(p => MyMethod(p));

或者在迭代操作 product 时调用 product.CreateTime.Date

AsEnumerable()的限制

如果你写了以下代码,

var products = db.Product                .Select(p => new {p.Id, p.Name, p.CreateTime})                .AsEnumerable()                .Select(p => new {p.Id, p.Name, p.CreateTime.ToString()});                .AsQueryable()                .Where(p=> p.Name == "xxx");

那么最后的 .Where()永远不会执行!!!
因为在使用 AsQueryable().Where() 要求 Linq to entity 进行数据库查询, 但是第一次 AsEnumerable() 时已经进行了数据库查询并且断开连接, 并且查询结果已经作为实实在在的 object, 对 object 不可能再次使用 AsQueryable().Where() 叠加数据库查询!

ToList()

调用 ToList() 会立刻查询并保存结果, 而不会等到迭代时才查询. 作用和 lazy load 是对立的.
在需要得到完整结果后, 再处理的场景, 需要使用 ToList().

例如, 在返回mvc ActionResult 的时候, 要先使用 ToList(), 再作为 model 传给 view. 不知道 mvc 是出于什么考虑不支持在生成 html 的时候lazy load, 请大家指教!


原创粉丝点击