本文将从什么是LINQ(What)、为什么使用LINQ(Why)以及如何使用LINQ(How)三个方面来进行说明。

1.什么是LINQ

LINQ(Language Integrated Query)是 Visual Studio 2008 中引入的一组功能,可为 C# 和 Visual Basic 语言语法提供强大的查询功能。 LINQ 引入了标准易学的数据查询和更新模式,可以扩展该方法来支持任何类型的数据存储。 Visual Studio 包括 LINQ 提供程序集,后者支持将 LINQ 与 .NET Framework 集合、SQL Server 数据库、ADO.NET 数据集和 XML 文档结合使用。说明来自MSDN

2.为什么使用LINQ

我们知道,一个程序一般可能都会有多种数据源,例如:关系型数据库、XML、JSON等。在LINQ出现以前,微软还没有一套成熟的Data Mapping解决方案。我们开始的做法是针对对不同的数据源写相应的Helper类,这大大增加了工作量,而LINQ正是为了解决这个问题产生的,它用一种统一的方式即可在相应的语言中对各种数据进行查询和更新。举个例子:

var query = from x in context.T_Developer
select new
{
Developer = x,
DepartmentName = x.T_Department.Name
};

这是一个LINQ to SQL简单的例子,我们以这种更OO的方式来获取数据库中的数据,而不需要去关心它是如何操作数据库的,而且,当改成其它数据源时,只需稍微改动就能正常运行。

3.如何使用LINQ

使用LINQ之前,首先要知道语法以及标准查询运算符,下面以几个LINQ to Object例子来说明:

var developers = new List<Developer>
{
new Developer {ID = 1, Name = "Jello", DepartmentID = 1},
new Developer {ID = 2, Name = "Taffy", DepartmentID = 2},
new Developer {ID = 3, Name = "Tom", DepartmentID = 1},
new Developer {ID = 4, Name = "Lily", DepartmentID = 1}
};
var query = from x in developers
select new
{
Developer = x,
NameLength = x.Name.Length
};

这是一个基本的LINQ to Object的例子,其中,Developers是内存中的集合。初次看到会觉得很亲切,这不就是SQL么!其实真不是这么回事。

上图对该查询表达式结构做了分解说明,这里涉及到几个知识点:

1.隐式类型的局部变量,如var query

2.匿名类型,如new {...}

3.类型推断,如query变量、x范围变量等,当然,都可以具体指定类型

4.扩展方法及lambda表达式,该查询表达式等价于:

var query = developers.Select(x => new
{
Developer = x,
NameLength = x.Name.Length
});

5.延迟加载,这个特性很重要,试想,当要统计一个4G的文本文件中每行相同的字符数量,如果直接将文本文件加载到内存,这是多么恐怖!而用延迟加载每行将大大减少内存消耗。

接下来,我们将会在上面查询表达式的基础上说明其它标准查询运算符。

3.1 排序和过滤

var query = from x in developers
where x.DepartmentID == 1
orderby x.Name
select new
{
Developer = x,
NameLength = x.Name.Length
};
foreach (var item in query)
{
Console.WriteLine("{0}--{1}", item.Developer.Name, item.NameLength);
} //Output:
Jello--5
Lily--4
Tom--3

where过滤表达式:过滤表达式会被转化为对Where扩展方法的调用,即当作进入数据流的每一个元素的谓词,只有返回true的元素才能出现在结果序列中。多个where字句时会同时起作用。

orderby排序表达式:orderby后面跟一个或多个排序规则,可以是ascending(默认)/descending,一个orderby字句中有多个排序规则时会转换为对扩展方法OrderBy(Descending)和ThenBy(Descending)的调用。当有多个orderby字句时,起作用的永远是最后一个。

3.2 联接和分组联接

我们知道,在SQL中联接(join)大致可分为inner join、outer join和cross join三种,其中outer join又可分为left outer join、right outer join和full outer join三种。在LINQ中,也有相同意义的实现。先准备数据:

var departments = new List<Department>
{
new Department {ID = 1, Name = "Product Department"},
new Department {ID = 2, Name = "Project Department"}
};

Inner join:

var query = from x in developers
join d in departments
on x.DepartmentID equals d.ID
select new
{
DeveloperName = x.Name,
DepartmentName = d.Name
};
foreach (var item in query)
{
Console.WriteLine("{0}-{1}", item.DeveloperName, item.DepartmentName);
} //Output:
Jello-Product Department
Taffy-Project Department
Tom-Product Department
Lily-Product Department

Left outer join,是通过使用into分组,然后DefaultIfEmpty()来实现的。为了使效果更明显,这里将ID = 2的Department从departments移除:

var query = from x in developers
join d in departments
on x.DepartmentID equals d.ID into dpts
from ds in dpts.DefaultIfEmpty()
select new
{
DeveloperName = x.Name,
DepartmentName = default(Department) == ds ? "No Department" : ds.Name
};
foreach (var item in query)
{
Console.WriteLine("{0}-{1}", item.DeveloperName, item.DepartmentName);
} //Output:
Jello-Product Department
Taffy-No Department
Tom-Product Department
Lily-Product Department

Right outer join的实现方式和Left outer join一样。

Full outer join其实Left outer join和Right outer join做Union的结果。

Cross join是一种隐式的行为,就是序列间做笛卡尔乘积:

var query = from x in developers
from d in departments
select new
{
DeveloperName = x.Name,
DepartmentName = d.Name
};
foreach (var item in query)
{
Console.WriteLine("{0}-{1}", item.DeveloperName, item.DepartmentName);
} //Output:
Jello-Product Department
Jello-Project Department
Taffy-Product Department
Taffy-Project Department
Tom-Product Department
Tom-Project Department
Lily-Product Department
Lily-Project Department

Group join,分组联接其实我们在Left outer join中已经用到了:

var query = from d in departments
join x in developers
on d.ID equals x.DepartmentID into groupedDeveloper
select new
{
Department = d,
GroupedDeveloper = groupedDeveloper,
Count = groupedDeveloper.Count()
};
foreach (var item in query)
{
Console.WriteLine("DepartmentName:{0},Count:{1}", item.Department.Name, item.Count);
foreach (var gd in item.GroupedDeveloper)
{
Console.WriteLine(" DeveloperID:{0},DeveloperName:{1}", gd.ID, gd.Name);
}
} //Output:
DepartmentName:Product Department,Count:3
DeveloperID:1,DeveloperName:Jello
DeveloperID:3,DeveloperName:Tom
DeveloperID:4,DeveloperName:Lily
DepartmentName:Project Department,Count:1
DeveloperID:2,DeveloperName:Taffy

在左联接查询表达式中,由于LINQ的延迟加载特性,在第一次迭代from字句序列时,会先缓存join字句序列,然后做匹配,之后的from字句序列迭代将从缓存匹配,所以,当一个大序列join一个小序列时,应该将大序列作为from字句序列,而将小序列作为join字句序列,这样小序列被缓存,而大序列由于延迟,内存消耗更小。

3.3 let字句

let字句,用于进行中间计算:

var query = from x in developers
let nameLength = x.Name.Length
orderby nameLength
select new
{
Developer = x,
NameLength = nameLength
};
foreach (var item in query)
{
Console.WriteLine("{0}--{1}", item.Developer.Name, item.NameLength);
} //Output:
Tom--3
Lily--4
Jello--5
Taffy--5

看起来很神奇,转化为等价的扩展方法就一目了然:

var list = developers.Select(x => new { Developer = x, nameLength = 	x.Name.Length })
.OrderBy(x => x.nameLength)
.Select(x => new { Developer = x.Developer, NameLength = x.nameLength });

3.4 Group分组

Group分组,使用 group [投影] by [分组] 字句进行分组:

var query = from x in developers
group new { x.ID, x.Name } by x.DepartmentID;
foreach (var group in query)
{
Console.WriteLine("DepartmentID:{0}", group.Key);
foreach (var item in group)
{
Console.WriteLine(" {0}--{1}", item.ID, item.Name);
}
} //Output:
DepartmentID:1
1--Jello
3--Tom
4--Lily
DepartmentID:2
2--Taffy

当我们想要使用另外一个投影来延续分组结果的时候,就要用到group by into来进行查询延续了。

var query = from x in developers
group x by x.DepartmentID into groupedDeveloper
select new
{
DepartmentID = groupedDeveloper.Key,
Count = groupedDeveloper.Count()
};
foreach (var item in query)
{
Console.WriteLine("DepartmentID:{0},Count:{1}", item.DepartmentID, item.Count);
} //Output:
DepartmentID:1,Count:3
DepartmentID:2,Count:1

最后举一个稍微复杂点儿的例子:开发人员按部门分组,哪组人多哪组靠前排列,然后同部门姓名短的靠前排列。

var query = from x in developers
group x by x.DepartmentID into groupDeveloper
let count = groupDeveloper.Count()
from g in groupDeveloper
let length = g.Name.Length
orderby count descending,length
select g;
foreach (var item in query)
{
Console.WriteLine("[{0}]{1}--{2}", item.DepartmentID, item.ID, item.Name);
} //Output:
[1]3--Tom
[1]4--Lily
[1]1--Jello
[2]2--Taffy

LINQ之路(1):LINQ基础的更多相关文章

  1. LINQ之路系列文章导读

    本系列文章将会分为3篇来进行阐述,如下: LINQ之路(1):LINQ基础 LINQ之路(2):LINQ to SQL本质 LINQ之路(3):LINQ扩展

  2. LINQ之路 7:子查询、创建策略和数据转换

    在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...

  3. [转]LINQ之路系列博客导航

    分享一个学习Linq的好博客:Linq之路

  4. LINQ之路 4:LINQ方法语法

    书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...

  5. LINQ之路(3):LINQ扩展

    本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符.自定义查询操作符和简单模拟LINQ to SQL. 1.扩展查询操作符 在实际的使用过程中,Enumerable或Queryable中的扩 ...

  6. LINQ之路(2):LINQ to SQL本质

    LINQ之路(2):LINQ to SQL本质 在前面一篇文章中回顾了LINQ基本语法规则,在本文将介绍LINQ to SQL的本质.LINQ to SQL是microsoft针对SQL Server ...

  7. 讲讲Linq to SQL映射(基础篇)

    讲讲Linq to SQL映射(基础篇) 这篇主要讲Linq to  SQL基于属性的映射.即映射数据库,映射表,映射列,映射关系,映射存储过程, 映射函数.然而创建这种映射有三种方法,他们分别是OR ...

  8. LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

    本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...

  9. LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

    本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...

  10. LINQ之路10:LINQ to SQL 和 Entity Framework(下)

    在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...

随机推荐

  1. Monkey源代码分析之执行流程

    在<MonkeyRunner源代码分析之与Android设备通讯方式>中.我们谈及到MonkeyRunner控制目标android设备有多种方法.当中之中的一个就是在目标机器启动一个mon ...

  2. c#与oracle数据库连接池

    c#与oracle数据库连接池 在做一个项目,中间要使用webservice和oracle数据库.我在服务端做了用户身份认证,也就是使用session传递用户的登陆信息.在测试时,当用户少的时候,没有 ...

  3. Spring Data Redis—Pub/Sub(附Web项目源码) (转)

    一.发布和订阅机制 当一个客户端通过 PUBLISH 命令向订阅者发送信息的时候,我们称这个客户端为发布者(publisher). 而当一个客户端使用 SUBSCRIBE 或者 PSUBSCRIBE ...

  4. OpenCV 编程简单介绍(矩阵/图像/视频的基本读写操作)

    PS. 因为csdn博客文章长度有限制,本文有部分内容被截掉了.在OpenCV中文站点的wiki上有可读性更好.而且是完整的版本号,欢迎浏览. OpenCV Wiki :<OpenCV 编程简单 ...

  5. alertify、js、css 使用简介

    Alertify.js which helped me resolve my issues regarding prompts, alerts, confirms, etc in iOS7. 1.al ...

  6. 用JavaScript实现网页动态水印

    1.基本原理 页面加载后,通过javascript创建页面元素div,并在div元素中创建文本节点,展示水印内容 设置div元素样式,将其zIndex设置一个较高的值,并设置透明度,实现浮在页面的水印 ...

  7. 递归遍历XML所有节点

    package xml; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.DocumentEx ...

  8. Gradle第二步骤来创建学习Task

    请下载本系列中的以下文章Github演示示例代码: git clone https://github.com/davenkin/gradle-learning.git     Gradle的Proje ...

  9. Quartz CronTrigger应用

    CronTrigger配置格式: 格式: [第二] [支] [小时] [日本] [月] [周] [年]  序号 说明  是否必填  同意填写的值 同意的通配符  1  秒  是  0-59    , ...

  10. Replace是替代 Split分割字符串

    Replace是替代 Split分割字符串string[] ReadText = str.Replace("\r\n", "@").Split('@'); Sp ...