什么是linq
语言集成查询 (LINQ) 是一组基于将查询功能直接集成到 C# 语言中的技术的名称。传统上,对数据的查询表示为简单的字符串,在编译时没有类型检查或 IntelliSense 支持。此外,您必须为每种类型的数据源学习不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等等。使用 LINQ,查询是一流的语言结构,就像类、方法、事件一样。您可以使用语言关键字和熟悉的运算符针对强类型的对象集合编写查询。LINQ 系列技术为对象 (LINQ to Objects)、关系数据库 (LINQ to SQL) 和 XML (LINQ to XML) 提供一致的查询体验。
之前我们查询集合中的数据一般会使用for或foreach语句来进行查询,Linq 使用查询表达式来进行查询,Linq 表达式比之前用for或forach的方式更加简洁,比较容易添加筛选条件。
将下列int集合整体每个元素扩大10倍
var num = new List<int>() { 1,2,3,4,5}; IEnumerable<int> query = num.Select(n => n * 10); foreach (int n in query) Console.WriteLine(n);
从上面的例子可以看出,linq集合在查询是简单了很多,并且很容易添加筛选条件。
linq原理
编译器是如何处理这些查询表达式的呢,为了理解好这个问题就要先解释一下linq的底层思想。
1.序列
序列是linq的基础。序列是通过过IEnumerable和IEnumerable<T>接口进行封装,如果某个类型实现了IEnumerable接口,就意味着它可以被迭代访问。序列就像一个数据的传送带,每次只能获取一个,知道你不想传了或者序列中没有数据了。序列和其他集合数据结构(比如列表和数组)之间最大的区别就是,当你从序列读取数据的时候,通常不知道还有多少数据项等待读取,或者不能访问任意的数据项——只能是当前的这个。在你看到一个linq查询表达式的时候,应该要想到它所涉及的序列:一开始总是存在至少一个序列,且通常在中间过程会转换为其他序列,也可能和更多的序列连接在一起。
举个例子,获取成年人姓名的表达式
var names = from person in people where person.Age >= 18 select person.Name;
foreach (var n in names) Console.WriteLine(n);
然后讲这个表达式分解成独立的步骤:每一个箭头代表一个序列。每个框都代表查询表达式的一个步骤。我们获取他整个家庭成员(用Person对象表示)。接着经过过滤后,序列就只包含成人了(还是用Person对象表示)。而最终的结果以字符串形式包含这些成人的名字。每个步骤就是得到一个序列,在序列上应用操作以生成新的序列。结果不是字符串"Holly"和"Jon"——而是IEnumerable <String>,这样,在从里面一个接一个获取元素的时候,将首先生成"Holly",其次得到"Jon"。
那么,序列为什么如此重要?这是因为它是数据处理的基础,让我们能够只在需要的时候才对数据进行获取和处理。
2.延迟执行
上面这个查询表达式在被创建的时候,不会处理任何数据的,也不会访问原始的人员列表,而是在内存中生成了查询到查询的表达式,每一个序列都是通过委托实例来表示的。只有在访问结果IEnumerable<string>的第一个元素的时候,整个车轮才开始向前滚动。(后面有附linq的源码感兴趣的话可以研究研究)。LINQ的这个特点称为延迟执行,当我们使用foreach循环语句去打印结果的时候,表达式才开始运行,Select转换才会为它的第一个元素调用Where转换。而Where转换会访问列表中的第一个元素,检查这个谓词是否匹配(在这个例子中,是匹配的),并把这个元素返回给Select。最后,依次提取出名称作为结果返回。
查询操作并不是在查询运算符定义的时候执行,而是在真正使用集合中的数据时才执行(如:在遍历集合时调用MoveNext方法和Current检查),再举个简单的例子
var num = new List<int>(); num.Add(1); IEnumerable<int> query = num.Select(n => n * 10); foreach (int n in query) Console.WriteLine(n); num.Add(2); foreach (int n in query) Console.WriteLine(n);
输出的结果是10 20
绝大部分标准的LINQ查询运算符都具有延迟加载这种特性,但也有例外:
- 那些返回单个元素或返回一个数值的运算符,如First或Count。
- 转换运算符:ToArray,ToList,ToDictonnary,ToLookup。
以上这些运算符都会触发LINQ语句立即执行,因为它们的返回值类型不支持延迟加载。
3.标准查询操作符
在LINQ中存在着大量的运算,即所谓的标准查询操作符,简单介绍几个常用的,这些linq的扩展方法源码附在后面可以自行研究。
1.Where
var list = query.Where(m => m.PID == corpID && m.type == 8);
2.OrderBy
var list = query.Where(m => m.PID == corpID && m.type == 8).OrderBy(m => m.PID);
3.join连接
var q2 = from u in dataContext.useinfo join d in dataContext.useDetails on u.id equals d.id select u; //join时必须将join后的表into到一个新的变量XX中,然后要用XX.DefaultIfEmpty()表示外连接。 //DefaultIfEmpty使用了泛型中的default关键字。default关键字对于引用类型将返回null,而对于值类型则返回0。 //对于结构体类型,则会根据其成员类型将它们相应地初始化为null(引用类型)或0(值类型) var q3 = from u in dataContext.useinfo join d in dataContext.useDetails on u.id equals d.id into f from c in f.DefaultIfEmpty() select c;
附件:linq源码 https://github.com/dotnet/runtime
【参考】《深入理解C#》