E N D
第18章 数据访问的新利器——语言集成查询技术 语言集成查询(Language Integrated Query,LINQ)是Visual Studio 2008和.NET Framework 3.5版中一项突破性的创新,它在对象领域和数据领域之间架起了一座桥梁。传统上,针对数据的查询都是以简单的字符串表示,而没有编译时类型检查或IntelliSense支持。此外,还必须针对不同的数据源学习不同的查询语言,如SQL数据库、XML文档、各种Web服务等。而LINQ引入了标准的、易于学习的查询和更新数据模式,可以对其技术进行扩展以支持几乎任何类型的数据存储。Visual Studio 2008包含LINQ Privider的程序集,这些程序集支持将LINQ与.NET Framework集合、SQL Server数据库、ADO.NET数据集和XML文档一起使用。
章节内容 • 18.1 什么是LINQ • 18.2 学习写自己的LINQ查询 • 18.3 LINQ to SQL
18.1 什么是LINQ • LINQ,是语言集成查询(Language Integrated Query)的简称,是 Visual Studio 2008 和.NET Framework 3.5版中一项突破性的创新。MicroSoft宣称LINQ在对象领域和数据领域之间架起了一座桥梁。
首先要说明什么是查询 • 查询是一种从数据源检索数据的表达式。现在,我们的程序中控制的数据可以属于不同的数据域。 • 例如,一个数组,一个对象图表(object graph),一个XML文件,一个数据库,一个文本文件,一个注册表值,一封电子邮件,简单对象访问协议(Simple Object Access Protocol)信息内容,一个微软的Excel表格等等,举不胜举。
不同数据域有不同的访问方法 • 当查询一个数据库时,我们会很自然地就用SQL。 • 当访问XML数据时,很自然地就用DOM或者XQuery。 • 为了定位某个对象图,遍历一个数组并且构建自己的算法。 • 使用特定的应用程序(APIs)来访问其他的数据域,比如说Office的Excel表格, • 最终的结果是,在访问不同的数据源时就有了不同的编程模型。
LINQ试图统一这些不同数据源的不同访问方法!LINQ试图统一这些不同数据源的不同访问方法! • LINQ是一个编程模型,无论是访问文件、XML、数据库、注册表、事件日志、活动目录,还是第三方的数据,都可以使用统一的方法进行访问。 • LINQ可以与所有不同形态、不同大小的数据一起工作,允许在所有这些数据上执行查询、设置和转换。 • 而且LINQ是集成在.NET编程语言中的一种特性,已经成为编程语言的一个组成部分。
事实上,第10章介绍的Visual Studio.Net 2008在语言方面的新特性,包括扩展方法、匿名类型、Lambda表达式、查询表达式等,大部分都是为了支持LINQ而做出的。
LINQ查询操作 • 由以下3个不同的操作步骤组成。 • 获取数据源。 • 创建查询。 • 执行查询。 • 而这3个步骤,实际上都是由LINQ提供程序来完成的。
4种提供程序 • LINQ to SQL • LINQ to XML • LINQ to Objects • LINQ to DataSet
18.2 学习写自己的LINQ查询 • 18.2.1 第一个LINQ查询 1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 2 Label1.Text = "" 3 ' 数据源 4 Dim numbers As Integer( ) = {0, 1, 2, 3, 4, 5, 6} 5 ' 创建查询(获取索引号为偶数的元素) 6 Dim numQuery = From num In numbers _ 7 Where num Mod 2 = 0 _ 8 Select num 9 ' 执行查询 10 For Each num In numQuery 11 Label1.Text &= num & vbCrLf 12 Next 13 End Sub
此示例将一个整数数组numbers用作数据源 • numQuery:查询变量,其本身不执行任何操作并且不返回任何数据。它只是存储在以后某个时刻执行查询时为生成结果而必需的信息。 • from子句:指定数据源。 • num:范围变量,表示数据源的后继元素 • where子句:应用筛选器。 • select子句:指定返回的元素的类型。
查询变量 • 在LINQ中,查询变量是任何存储查询而不是查询结果的变量。 • 更具体地说,查询变量始终是一个可枚举的类型,当在For each语句中或对其IEnumerator.MoveNext方法的直接调用中循环访问它时,它将产生一个元素序列。 • 上一个例子中的numQuery就是一个查询变量,简称查询。
查询表达式 • 查询表达式是用查询语法表示的查询。 • 由一组用类似于SQL或XQuery的声明性语法编写的子句组成。每个子句又包含一个或多个表达式,而这些表达式本身又可能是查询表达式或包含查询表达式。
查询表达式语法 • 查询表达式必须以From子句开头,并且必须以Select或Group子句结尾。 • 在第一个From子句和最后一个Select或Group子句之间,查询表达式可以包含一个或多个可选子句:Where、Orderby、Join、Let甚至附加的From子句。 • 还可以使用Into关键字使Join或Group子句的结果能够充当同一查询表达式中附加查询子句的源。
From子句:指定数据源 From element [ As type ] In collection [ _ ] [, element2 [ As type2 ] In collection2 [ , ... ] ] • element是必需的,这是一个范围变量,用于循环访问集合的元素,必须为可枚举类型。该范围变量用于在查询循环访问collection时,引用collection的每个成员。 • type是可选的,用于指明element的类型。如果不指定type,则根据collection推断element的类型。 • collection是必需的。这是引用要查询的集合,必须为可枚举类型。
Where子句:筛选数据 • Where子句用于执行筛选,筛选器指定要在结果序列中包含数据源中的哪些元素。Where子句的语法格式如下。 • Where condition • 其中,condition是一个表达式,该表达式的计算结果必须为Boolean值或Boolean值的等效值。如果条件的计算结果为True,则在查询结果中包含该元素;否则从查询结果中排除该元素。
Order By子句:对数据进行排序 Order By orderExp1 [ Ascending | Descending ] [, orderExp2 [...] ] • 其中,orderExp1是必需的,这是当前查询结果中的一个或多个字段,用于标识对返回值进行排序的方式,字段名称必须以逗号(,)分隔; • 使用Ascending或Descending关键字可以指定对每个字段进行升序或降序排序。如果未指定Ascending和Descending关键字,则默认排序顺序为升序。排序顺序字段的优先级从左到右依次降低。
Select子句:选择数据 • Select [ var1 = ] fieldName1 [, [ var2 = ] fieldName2 [...] ] • 其中,var1是可选的,可用于引用列表达式的结果的别名;fieldName1是必需的,是要在查询结果中返回的字段的名称。
Group By子句:对数据进行分组 Group [ listField1 [, listField2 [...] ] By keyExp1 [, keyExp2 [...] ] Into aggregateList • listField1,listField2是可选的,用于指明查询变量的一个或多个字段,这些查询变量显式标识要包括在分组结果中的字段。如果未指定任何字段,则查询变量的所有字段都包括在分组结果中。
keyExp1是必需的,这是一个表达式,标识用于确定元素的分组的键。可以指定多个键来指定一个组合键。keyExp1是必需的,这是一个表达式,标识用于确定元素的分组的键。可以指定多个键来指定一个组合键。 • keyExp2是可选的,是一个或多个附加键,与keyExp1组合在一起,创建一个组合键。 • aggregateList是必需的,是一个或多个表达式,标识如何对组进行聚合。若要为分组结果标识一个成员名称,可以使用Group关键字。
Let子句:存储查询结果 • Let variable = expression [, ...] • 其中,variable是必需的,这是一个别名,可用于引用所提供的表达式的结果;expression是必需的,这是一个将进行计算并赋值给指定变量的表达式。
执行查询 • 查询执行与查询创建是分开的。创建查询后,其执行由不同的机制触发。 • 可在定义查询后立即执行查询(立即执行) • 也可以存储查询定义,并在以后执行查询(延迟执行)。
立即执行 • 默认情况下,创建查询后,查询本身并不立即执行。相反,查询定义将存储在用于引用查询结果的变量中。当以后在代码中访问查询结果变量时(如在For…Next循环中),将执行该查询,此过程称为延迟执行。之前我们看到的例子都是延迟执行方式。
延迟执行 • 查询还可以在定义后执行,这称为立即执行。立即执行可以通过应用要求访问查询结果的各个元素的方法来触发,这是包含聚合函数(如Count、Sum、Average、Min或Max)的结果。
18.3 LINQ to SQL • LINQ to SQL全称为基于关系数据的.NET语言集成查询,是.NET Framework 3.5版的一个组件,提供了用于将关系数据作为对象管理的运行时基础结构。
LINQ to SQL编程接口集中在System.Data.Linq.dll程序集中,要想使用LINQ to SQL,必须在项目中引用该程序集,命名空间为“System.Data.Linq”。
18.3.1 创建对象模型 • 创建LINQ to SQL对象模型是使用LINQ to SQL的第一步,也是最重要的一步。对象模型实际是数据库的一个映射关系和操作集合。
3种创建对象模型的方式 • (1)对象关系设计器。 • (2)SQLMetal代码生成工具。 • (3)代码编辑器。
第一种方式 • 首先需要建立与数据库的连接。可以单击菜单栏中的【工具】→【连接到数据库】来连接到你的数据库或者在【服务器资源管理器】窗口中建立数据库连接。
之后,单击菜单栏中的【项目】→【添加新项】命令,打开【添加新项-LINQtoSQLExample】窗口,在【模板】列表框中选择【LINQ to SQL类】,在【名称】文本框中输入“Student.dbml”。 • 其中,扩展名dbml的全称为Database Mark Language,即数据库描述语言,是一种xml格式的文档,用来描述数据库。
单击【添加】按钮以后,读者会看到dbml文件的窗口与以前我们所见到的设计窗口有些不一样。单击【添加】按钮以后,读者会看到dbml文件的窗口与以前我们所见到的设计窗口有些不一样。 • 该设计窗口分了两部分:左边是数据类的可视化窗口,右边是方法的创建窗口
我们将StudentInfo表从【服务器资源管理器】窗口中直接拖曳到右边的数据类可视化窗口,即可创建一个数据类。我们将StudentInfo表从【服务器资源管理器】窗口中直接拖曳到右边的数据类可视化窗口,即可创建一个数据类。
查看代码可以看到,生成了一个StudentInfoDataContext类。该类继承自继承自System.Data.Linq.DataContext。查看代码可以看到,生成了一个StudentInfoDataContext类。该类继承自继承自System.Data.Linq.DataContext。 • DataContext类是一个LINQ to SQL类,它充当SQL Server数据库与映射到该数据库的LINQ to SQL实体类之间的管道 • 生成的实体类代码如下。 1 Partial Class StudentInfoDataContext 2 3 End Class
18.3.2 获取数据 下面代码是窗体的Load事件过程,使用LINQ获取数据并输出。 1 Imports System.Data.Linq 2 Public Class Form1 3 4 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 5 ' 实例化LINQ to SQL对象 6 Dim db As New StudentInfoDataContext( ) 7 ' 得到一个表 8 Dim studentList As Table(Of StudentInfo) = db.GetTable(Of StudentInfo)( ) 9 ' LINQ查询 10 Dim query = From student In studentList _ 11 Select student.StuNum, student.StuName 12 ' 输出 13 For Each student In query 14 Label1.Text &= "学号 = " & student.StuNum & ", 姓名 = " & _ 15 student.StuName & vbCrLf 16 Next 17 End Sub 18 End Class
使用DataGridView控件来进行数据显示。 1 ' 实例化LINQ对象 2 Dim db As New StudentInfoDataContext() 3 ' 得到一个表 4 Dim studentList As Table(Of StudentInfo) = db.GetTable(Of StudentInfo)() 5 ' LINQ查询 6 Dim query = From student In studentList _ 7 Select student.StuNum, student.StuName 8 ' 绑定数据源 9 DataGridView1.DataSource = query
18.3.3 添加数据 • 如果想要往数据库表中添加一条记录,只要向已创建的表对象模型添加一个新学生对象,然后调用StudentInfoDataContext对象的SubmitChanges方法即可。
下面代码实现了记录的添加。 1 Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As _ 2 System.EventArgs) Handles btnAdd.Click 2 Dim db As New StudentInfoDataContext( ) 3 ' 新学生 4 Dim newStudent As New StudentInfo 5 newStudent.StuNum = "20083201" 6 newStudent.StuName = "李克" 7 ' 将新学生添加到Insert命令中 8 db.StudentInfo.InsertOnSubmit(newStudent) 9 ' 执行 10 db.SubmitChanges( ) 11 End Sub
18.3.4 更新数据 • 如果要更新某一条记录内容,首先需要检索到该项,然后直接在对象模型中编辑它。在修改了该对象之后,调用StudentInfoDataContext对象的SubmitChanges方法以更新数据库。
下面代码实现记录的更新。 1 Dim db As new StudentInfoDataContext( ) 2 ' 找到要修改的记录 3 Dim studentToUpdate = _ 4 from stu in db.StudentInfo _ 5 where stu.StuNum="20083201" _ 6 select stu 7 ' 对找到的记录进行修改 8 for each student in studentToUpdate 9 student.Sex = "男" 10 student.Age=21 11 student.Major="数学" 12 student.Class="0821" 13 Next 14 ' 提交修改 15 db.SubmitChanges( );
18.3.5 删除数据 • 如果要删除某条记录,要先从其所属集合中移除,然后调用StudentInfoDataContex对象的SubmitChanges方法以提交所做的更改。注意,LINQ to SQL无法识别级联删除操作。
下面代码实现记录的删除。 1 Dim db As new StudentInfoDataContext( ) 2 ' 找到要删除的记录 3 Dim studentToUpdate = _ 4 from stu in db.StudentInfo _ 5 where stu.StuNum= "20080002" _ 6 select stu 7 ' 如果找到了符合条件的记录则进行删除 8 for each student in studentToUpdate 9 db.StudentInfo.DeleteOnSubmit(student) 10 Next 11 ' 提交删除 12 db.SubmitChanges( )