
.NET Language-Integrated Query(LINQ,语言集成查询)是微软公司提供的一项新技术,它将查询操作直接引入到.NET Framework 3.5所支持的编程语言(如C#、Visual Basic等)中,从而直接实现查询功能。本章将讲解使用LINQ处理的方法,主要介绍以下知识点:
* LINQ概述;
* LINQ查询子句;
* 使用LINQ维护User表的数据。
通过本章的学习,读者将可以了解使用LINQ查询集合中的数据的基本操作方法,并使用LINQ处理数据库中的数据的方法。
13.1 LINQ概述
查询(Query)是一种从给定的数据源中检索满足指定条件的数据表达式。查询也是一组指令,这些指令可以从一个或多个给定的数据源中检索数据,并指定检索结果的数据类型和表现形式。
传统上,查询技术往往使用字符串来表示查询操作,如查询关系数据库的SQL语句、查询XML结构数据的XQuery等。在这些查询操作中,一般不会检查被查询数据的类型。同时,这些查询操作往往与编程语言处于一种相对孤立的状态。
LINQ(.NET Language-Integrated Query、语言集成查询)也是一种查询技术,它最大的特点就是能够把查询操作直接引入到.NET Framework 3.5所支持的编程语言(如C#、Visual Basic等)中,并整合为一体。LINQ查询表达式是由一组用类似于SQL或XQuery的声明性语法编写的子句组成。每一个子句可以包含一个或多个C#表达式,而这些表达式本身又可能是查询表达式或包含查询表达式。LINQ为.NET Framekwork 3.5所支持,它包括以下4个主要组件或技术。
* LINQ to Objects:查询IEnumerable或IEnumerable * LINQ to SQL:查询和处理(如插入、修改、删除、排序等操作)基于关系数据库(如SQL Server数据库等)的数据。 * LINQ to DataSet:查询和处理DataSet对象中的数据,并这些数据进行检索、过滤和排序等操作。 * LINQ to XML:查询和处理XML结构的数据(如XML文档、XML数据片段、XML格式的字符串等)。 【示例13-1】 创建一个简单的LINQ查询表达式query0。该表达式查询sources数组中小于3的元素。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query0 = from v in sources where v < 3 select v; 13.2 LINQ查询子句 LINQ查询表达式由一组类似于SQL或XQuery的声明性语法编写的子句组成。每一个子句可以包含一个或多个C#表达式,而这些表达式本身又可能是查询表达式或包含查询表达式。LINQ查询表达式包含8个基本子句,具体说明如下所述。 * from子句:指定查询表达式的数据源和范围变量。 * select子句:指定筛选元素的逻辑条件。一般由逻辑运算符组成。 * where子句:指定查询结果的表现形式。 * group子句:对查询结果进行分组。 * orderby子句:对查询结果进行排序。 * join子句:用来连接多个查询操作的数据源。 * let子句:可以用来引入用于存储子表达式查询结果的范围变量。 * into子句:提供一个临时标识符,使用该标识可以允许对join、group或select子句结果的引用。 13.2.1 from子句 from子句和SQL语句中的“FROM子句”比较相似,它可以指定查询的数据源(也包括子查询的数据源)和范围变量。其中,范围变量用来表示数据源序列中的每一个元素。 ?注意:LINQ查询表达式必须包含from子句,且以from子句开头。 【示例13-2】 创建了一个LINQ查询表达式,并保存为query1。该表达式从sources数组中查询小于2的元素。 ?注意:s为范围变量。sources为数据源,它为整型数组,并包含10个值。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query1 = from s in sources where s < 2 select s; 示例13-2的查询结果如表13.1所示。 表13.1 示例13-2的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s < 2 select s; query1(包含两个元素,分别为0和1) 【示例13-3】 创建了一个LINQ查询表达式,并保存为query2。该表达式从strs字符串中查询a字符到g之间的字符。 ?注意:c为范围变量。strs为数据源,它为一个字符串,值为“This is a string.”。 string strs = "This is a string."; var query2 = from c in strs where c <= 'g' && c >= 'a' select c; 示例13-3的查询结果如表13.2所示。 表13.2 示例13-3的查询结果表 数 据 源 查询表达式 查 询 结 果 strs,值为“This is a string.” from c in strs where c <= 'g' && c >= 'a' select c; query2(包含两个元素,分别为“a”和“g”) ?注意:from子句指定数据源的类型必须为IEnumerable、IEnumerable 根据from子句的数量和组合方式,可以把LINQ查询表达式分为以下3种类型。 * 单个from子句查询表达式。 * 多个from子句查询表达式。 * 复合from子句查询表达式。 1.单个from子句查询 若LINQ查询表达式只包含一个from子句,则称该查询为单个from子句查询。一般情况下,单个from子句查询往往使用一个数据源。 【示例13-4】 创建了一个LINQ查询表达式,并保存为query3。该表达式从strs字符串中查询a字符到c之间的字符。 string strs = "This is a string."; var query3 = from c in strs where c <= 'c' && c >= 'a' select c; 示例13-4的查询结果如表13.3所示。 表13.3 示例13-4的查询结果表 数 据 源 查询表达式 查 询 结 果 strs,值为“This is a string.” from c in strs where c <= 'c' && c >= 'a' select c; query3(包含1个元素,为“a”) 2.多个from子句查询 若LINQ查询表达式包含多个from子句,则称该查询为多个from子句查询。一般情况下,包含多个from子句的查询往往使用多个数据源。 【示例13-5】 创建了一个LINQ查询表达式,并保存为query4。该表达式从sourcesa数组中查询小于2的元素,并从sourcesb数组中查询大于100的元素,并计算两个查询的和。 int[] sourcesa = {0,1,2,3,4,5,6,7,8,9}; int[] sourcesb = {100,200,300,400,2008}; var query4 = from s in sourcesa where s < 2 from i in sourcesb where i > 100 select s + i; 示例13-5的查询结果如表13.4所示。 表13.4 示例13-5的查询结果表 数 据 源 查询表达式 查 询 结 果 sourcesa(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) sourcesb(包括5个元素,分别为100、200、300、400、2008) from s in sourcesa where s < 2 from i in sourcesb where i > 100 select s + i; query4(包含8个元素,分别为200、300、400、2008、201、301、401、2009) 3.复合from子句查询 在一些情况下,数据源的元素也是一个数据源(被称为子数据源)。如果要查询子数据源中的元素,则需要使用复合from子句。 【示例13-6】 创建了一个LINQ查询表达式,并保存为query5。该表达式首先从sources字符数组中查询包含string的字符串(使用“from s in sources”子句),然后从该字符串中查询a字符到g之间的字符(使用“from c in s”子句)。最后,输出查询结果的字符及其所在的字符串。 string[] sources = {"This is a string. var query5 = from s in sources from c in s where s.IndexOf("string") >-1 && c <= 'g' && c >= 'a' select c + ":" + s; 示例13-6的查询结果如表13.5所示。 表13.5 示例13-6的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含2个元素,分别为“This is a string.”和“This is a book.”) from s in sources from c in s where s.IndexOf("string") >-1 && c <= 'g' && c >= 'a' select c + ":" + s; query5(包含2个元素,分别为“a:This is a string.”和“g:This is a string.”) 13.2.2 where子句 where子句和SQL语句中的“WHERE子句”比较相似,它指定查询表达式的筛选元素满足的逻辑条件。where子句一般由逻辑运算符(如逻辑“与”、逻辑“或”)组成。一个查询表达式可以包含1个或多个where子句,甚至不包含where子句。每一个where子句可以包含1个或多个布尔条件表达式。 ?注意:对LINQ查询表达式而言,where子句不是必须的。如果LINQ查询表达式包含了where子句,那么where子句不能为LINQ查询表达式的第一个子句或最后一个 子句。 【示例13-7】 创建了一个LINQ查询表达式,并保存为query6。该表达式从sources数组中查询大于3的元素。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query6 = from s in sources where s > 3 select s; 示例13-7的查询结果如表13.6所示。 表13.6 示例13-7的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s > 3 select s; query6(包含6个元素,分别为4、5、6、7、8、9) 【示例13-8】 创建了一个LINQ查询表达式,并保存为query7。该表达式从sources数组中查询大于3且小于8的元素。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query7 = from s in sources where s > 3 && s < 8 select s; 示例13-8的查询结果如表13.7所示。 表13.7 示例13-8的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s > 3 && < 8 select s; query7(包含4个元素,分别为4、5、6、7) 13.2.3 select子句 select子句和SQL语句中的“SELECT子句”比较相似,它可以指定查询结果的类型和表现形式。一个LINQ查询表达式可以不包含select子句,也可以包含1个select子句。 ?注意:LINQ查询表达式要么以select子句结束,要么以group子句结束。 【示例13-9】 创建了一个LINQ查询表达式,并保存为query8。该表达式从sources数组中查询大于7的元素。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query8 = from s in sources where s > 7 select s; 示例13-9的查询结果如表13.8所示。 表13.8 示例13-9的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s > 7 select s; query8(包含2个元素,分别为8和9) 【示例13-10】 创建了一个LINQ查询表达式,并保存为query9。该表达式从sources数组中查询小于3的元素,并返回查询结果的元素与100的乘积。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query9 = from s in sources where s < 3 select s * 100; 示例13-10的查询结果如表13.9所示。 表13.9 示例13-10的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s < 3 select s * 100; query8(包含3个元素,分别为0、100、200) 【示例13-11】 创建了一个LINQ查询表达式,并保存为query10。该表达式从sources数组中查询小于3的元素,并返回新对象。其中,新对象的ID属性值为查询结果的元素值;Username属性的值则由“User”字符串和查询结果的元素的值组成。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query10 = from s in sources where s < 3 select new { ID = s, Username = "User" + s.ToString() }; 示例13-11的查询结果如表13.10所示。 表13.10 示例13-11的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s < 3 select new UserInfo { ID = s, Username = "User" + s.ToString() };; query10(包含3个元素,元素的ID属性的值分别为0、1、2) 13.2.4 group子句 group子句可以对查询的结果进行分组,并返回元素类型为IGrouping ?注意:IGrouping 【示例13-12】 创建了一个LINQ查询表达式,并保存为query11。该表达式从sources数组中查询元素,并按照元素的奇偶性进行分组。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query11 = from s in sources group s by s % 2 == 0; 【示例13-13】 使用2个foreach语句输出了query11查询的结果。第1个foreach语句枚举query11查询结果中的所有的组,第2个foreach语句枚举每一个组中的每一个元素。 foreach(var q in query11) { foreach(var subq in q) { Response.Write(subq.ToString() + } Response.Write(" } 13.2.5 orderby子句 orderby子句可以按照关键字(或键)对查询结果进行排序,其中,排序方式可以为“升序”或“降序”,排序的关键字(或键)可以为一个或多个。 ?注意:order子句的默认排序方式为“升序”。 【示例13-14】 创建了一个LINQ查询表达式,并保存为query12。该表达式从sources数组中查询大于7的元素,并按照元素进行升序排序。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query12 = from s in sources where s > 7 order by s select s; 示例13-14的查询结果如表13.11所示。 表13.11 示例13-14的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources where s > 7 order by s select s; query12(包含2个元素,分别为8和9) 【示例13-15】 创建了一个LINQ查询表达式,并保存为query13。该表达式从sources数组中查询元素,并按照元素的奇偶性进行分组。其中,每一个分组都按照元素的值进行倒序排序。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query13 = from s in sources orderby s descending group s by s % 2 == 0; 13.2.6 into子句 into子句可以创建一个临时标识符,使用该标识符可以存储group、join或select子句查询结果。 【示例13-16】 创建了一个LINQ查询表达式,并保存为query14。该查询表达式的具体操作说明如下所述。 * 从sources数组中查询元素。 * 按照元素的奇偶性进行分组。 * 每一个分组中的元素都按照元素的值进行倒序排序。 * 将每一个查询结果临时保存为g变量。 * 使用where子句判断查询g变量中的元素数量是否大于5,如果大于5,则选择该组。 int[] sources = {0,1,2,3,4,5,6,7,8,9,10}; var query14 = from s in sources orderby s descending group s by s % 2 == 0 into g where g.Count() > 5 select g; 示例13-16的查询结果如表13.12所示。 表13.12 示例13-16的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含10个元素,分别为0、1、2、3、4、5、6、7、8、9、10) from s in sources orderby s descending group s by s % 2 == 0 into g where g.Count() > 5 select g; query14(包含1个元素,该元素为一个组,组内包含6个元素,分别为10、8、6、4、2、0) 13.2.7 let子句 let子句可以创建一个范围变量,使用该变量可以保存表达式中的中间结果。 ?注意:let子句创建的范围变量的值只能通过初始化操作进行赋值。范围变量的值一旦被初始化,将不能再被改变。 【示例13-17】 创建了一个LINQ查询表达式,并保存为query15。该查询表达式的具体操作说明如下所述。 * 从sources数组中查询元素。 * 使用let语句创建范围变量m,它的值为元素除以2的余数。 * 查询元素的值大于3,且m变量的值等于1的元素。 int[] sources = {0,1,2,3,4,5,6,7,8,9}; var query15 = from s in sources let m = s % 2 where s > 3 && m == 1 select s; 示例13-17的查询结果如表13.13所示。 表13.13 示例13-17的查询结果表 数 据 源 查询表达式 查 询 结 果 sources(包含9个元素,分别为0、1、2、3、4、5、6、7、8、9) from s in sources let m = s % 2 where s > 3 && m == 1 select s; query15(包含3个元素,分别为5、7和9) 13.2.8 join子句 join子句可以设置两个或多个存在相关属性或值的数据源之间的关系。join子句可以为数据源之间建立以下3种联接关系。 * 内部联接,和SQL语句中的“INNER JOIN子句”比较相似,两个数据源都必须存在相同的值,即两个数据源都必须存在满足联接关系的元素。 * 分组联接,即为含有into子句的join子句。 * 左外部联接,和SQL语句中的“INNER LEFT子句”比较相似。 【示例13-18】 创建两个类:UserInfo和Role- Info。UserInfo类描述用户的信息,包含3个属性:ID、RoleID和Username。RoleInfo类描述角色的信息,包含2个属性:ID和RoleName。UserInfo和RoleInfo类的类关系图如图13.1所示。 ?注意:UserInfo类的RoleID属性的值是引用于RoleInfo类的ID属性。因此,UserInfo类的RoleID属性和RoleInfo类的ID属性将建立联接关系。 /// public class RoleInfo { private int id; private string roleName; /// public int ID{get { return id; }set { id = value; }} /// public string RoleName{get { return roleName; }set { roleName = value; }} } /// public class UserInfo { private int id; private string username; private int roleID; /// public int ID{get { return id; }set { id = value; }} /// public string Username{get { return username; }set { username = value; }} /// public int RoleID{get { return roleID; }set { roleID = value; }} } 【示例13-19】 创建两个泛型集合:users和roles,它们的类型分别为List ///构建数据源 System.Collections.Generic.List Generic.List System.Collections.Generic.List Generic.List for(int i = 1; i < 10; i++) { ///创建UserInfo类的实例 UserInfo ui = new UserInfo(); ui.ID = i; ui.RoleID = i * 2; ui.Username = "User0" + i.ToString(); users.Add(ui); ///创建RoleInfo类的实例 RoleInfo ri = new RoleInfo(); ri.ID = i; ri.RoleName = "Role0" + i.ToString(); roles.Add(ri); } 1.内部联接 内部联接要求两个数据源都必须满足联接关系的元素。如果不存在满足联接关系的元素,则两个数据源的内部联接之后,数据源的元素数量为空,即联接之后的数据源为空。 【示例13-20】 使用join子句联接了users和roles数据源,联接关系为“相等(equal)”,并查询ID属性值小于9的元素。 ///查询ID值小于9,且角色包含在roles中的用户 var query16 = from u in users where u.ID < 9 join r in roles on u.RoleID equals r.ID select u; 2.分组联接 分组联接是指包含into子句的join子句的联接。分组联接产生分层结构的数据,它将第一个数据源中的每个元素与第二个数据源中的一组相关元素进行匹配。第一个数据源中的元素都会出现在查询结果中。如果第一个数据源中的元素在第二个数据源中找到相关元素,则使用被找到的元素,否则使用空。 【示例13-21】 使用join子句联接了users和roles数据源,联接关系为“相等(equal)”,查询操作具体说明如下: * 从users数据源中查询元素。 * 使用join子句联接了users和roles数据源,联接关系为“相等(equal)”,并设置组的标识为g。 * 使用select子句查询一个新类型的数据。其中,Roles属性的值为分组g的值(为一个集合)。 ///查询ID值小于9,且角色包含在roles中的用户 var query17 = from u in users where u.ID < 9 join r in roles on u.RoleID equals r.ID into g select new { ID = u.ID, Username = u.Username, RoleID = u.RoleID, Roles = g.ToList() }; 3.左外部联接 左外部联接和SQL语句中的LEFT JOIN比较相似,它将返回第1个数据源中的每一个元素,而无论第2个数据源中是否存在相对应的相关元素。 【示例13-22】 使用join子句联接了users和roles数据源,联接关系为“相等(equal)”,查询操作具体说明如下: * 从users数据源中查询元素。 * 使用join子句联接了users和roles数据源,联接关系为“相等(equal)”,并设置组的标识为g。 * 使用from子句选择分组g中的默认元素。 * 使用select子句查询一个新类型的数据。其中,Roles属性的值为分组g的值(为一个集合)。 ///查询ID值小于9,且角色包含在roles中的用户 var query17 = from u in users where u.ID < 9 join r in roles on u.RoleID equals r.ID into g from ur in g.DefaultIfEmpty() select new { ID = u.ID, Username = u.Username, RoleID = u.RoleID, Roles = g.ToList() }; 13.3 使用LINQ实现用户管理模块 本章使用LINQ实现用户管理模块,它包括4个功能:查询用户、添加新用户、修改用户和删除用户,它们分别由SelectUser.aspx、AddUser.aspx、UpdateUser.aspx和DeleteUser.aspx页面实现。 13.3.1 数据库实体类设计 下面介绍为FirstSQLServerDB数据库创建数据库实体类的方法,即为该数据库创建一个名称为FirstSQLServerDB.dbml的DBML文件。具体步骤如下所述。 (1)右击“解决方案资源管理器”面板中的App_Code分支,并选择“添加新项”命令,弹出“添加新项-D:\\BookCode\\ASP.NET3.0Prime\\Sample_13\”对话框,如图13.2所示。 图13.2 “添加新项-D:\\BookCode\\ASP.NET3.0Prime\\Sample_13\”对话框 (2)在“名称”文本框中输入“FirstSQLServerDB.dbml”。 (3)单击“添加”按钮,关闭“添加新项-D:\\BookCode\\ASP.NET3.0Prime\\Sample_13\”对话框,并创建FirstSQLServerDB.dbml文件。 (4)打开“服务器资源管理器”面板,并找到指示FirstSQLServerDB数据库的分支。 (5)展开该分支至“表”和“存储过程”分支,如图13.3所示。 (6)将FirstSQLServerDB数据库的所有表和存储过程直接拖放到FirstSQLServerDB.dbml文件的视图面板中,如图13.4所示。保存该文件即可创建FirstSQLServerDB.dbml文件。 图13.3 “服务器资源管理器”面板 图13.4 FirstSQLServerDB.dbml文件的视图 ?注意:创建FirstSQLServerDB.dbml文件之后,在“解决方案资源管理器”面板中的App_ Code系统文件中打开其代码设计文件FirstSQLServerDB.designer.cs文件,并添加新的命名空间“Sample_13”。 13.3.2 使用LinqDataSource控件访问数据库实体类 【实例13-1】 模块中的ShowUser.aspx页面使用LinqDataSource控件访问数据库实体类中的User表,并为GridView控件提供数据。最终,ShowUser.aspx页面显示User表中的记录,具体步骤如下所述。 (1)在Visual Studio 2008集成开发环境中,打开名称为“Sample_13”的ASP.NET网站。 (2)右击“解决资源方案管理器”面板中的“D:\\...\\Samle_13”节点,弹出“添加新项-D:\\BookCode\\ASP.NET3.0Prime\\ch13\\Sample_13\”对话框。选中“Web 窗体”图标,并在“名称”文本框中输入“ShowUser.aspx”。单击“添加”按钮,可以将ShowUser.aspx页面添加到Sample_13网站。 (3)打开SelectUser.aspx页面的“源”视图,并设置该页面的标题为“使用LinqDataSource控件访问数据库实体类”。 (4)在ShowUser.aspx页面声明一个名称为gvUser的GridView控件。下面介绍为gvUser控件配置名称为linqDSUser数据源的方法,具体步骤如下所述。 * 单击gvUser控件的<按钮,显示智能提示菜单,如图13.5所示。 * 选择“选择数据源”下拉列表框中的“新建数据源”列表项,打开“选择数据源类型”对话框。选择“LINQ”图标,并设置数据源的ID值为linqDSUser,如图13.6所示。 * 单击“确定”按钮,打开“选择上下文对象”对话框。在其中选择“FirstSQLServer- DBDataContext”列表项,如图13.7所示。 * 单击“下一步”按钮,打开“配置数据选择”对话框。选择User表的ID、Username、Email和Status列,如图13.8所示。 * 单击“完成”按钮,即可以配置好linqDSUser数据源。 图13.5 gvUser控件的智能提示 图13.6 “选择数据源类型”对话框 图13.7 “选择上下文对象”对话框 图13.8 “配置数据选择”对话框 综上所述,ShowUser.aspx页面的HTML设计代码如下: <%@ Page Language="C#" %> (5)把ShowUser.aspx页面设置为Sample_13网站的起始页面,并运行该网站。在IE浏览器中查看ShowUser.aspx页面,如图13.9所示。此时,该页面显示User表中的记录。 图13.9 ShowUser.aspx页面显示User表中的记录 13.3.3 查询用户 【实例13-2】 模块中的SelectUser.aspx页面使用LINQ实现查询用户功能。该页面使用Grid- View控件显示LINQ查询表达式的查询结果,具体步骤如下所述。 (1)在Visual Studio 2008集成开发环境中,打开名称为Sample_13的ASP.NET网站。 (2)右击“解决资源方案管理器”面板中的“D:\\...\\Samle_13”节点,弹出“添加新项-D:\\BookCode\\ASP.NET3.0Prime\\ch13\\Sample_13\”对话框。选中“Web 窗体”图标,并在“名称”文本框中输入“SelectUser.aspx”。单击“添加”按钮,可以将SelectUser.aspx页面添加到Sample_13网站。 (3)打开SelectUser.aspx页面的“源”视图,并设置该页面的标题为“查询用户”。 (4)向SelectUser.aspx页面添加1个GridView控件,ID属性的值为gvUser。SelectUser.aspx页面的HTML设计代码如下: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SelectUser.aspx.cs" Inherits="SelectUser" %>
");