4-2          ADO.NET-查询和检索数据<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

    在上一节中,我们介绍了ADO.NET的基本组成结构和特点,详细阐述了ADO.NET的五大基本对象,重点论述了ADO.NET数据库访问的连线式连接方式和断线式连接方式,详细阐述了Connection 对象和Command 对象,介绍了ADO.NET的事务处理原理与实践。

u  本节学习目标:

n  掌握DataSet对象

n  DataTableDataColumnDataRow对象

n  DataAdapter对象

n  DataReader对象

<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />4-2-1  DataSet对象

1.数据集DataSet概述

数据集DataSet是断开与数据源的连接时,可以被使用的数据记录在内存中的缓存。前面在4-1-3节断开式数据库访问连接部分提到,可以把数据集DataSet看作是内存中的数据库。它在应用程序中对数据的支持功能十分强大。DataSet一经创建,就能在应用程序中充当数据库的位置,为应用程序提供数据支持。

数据集DataSet的数据结构可以在.net开发环境中通过向导完成,也可以通过代码来增加表、数据列、约束以及表之间的关系。数据集DataSet中的数据既可以来自数据源,也可以通过代码直接向表中增加数据行。这也看出,数据集DataSet类似一个客户端内存中的数据库,可以在这个数据库中增加、删除数据表,可以定义数据表结构和表之间的关系,可以增加、删除表中的行。

数据集DataSet不考虑其中的表结构和数据是来自数据库、XML文件还是程序代码,因此数据集DataSet不维护到数据源的连接。这缓解了数据库服务器和网络的压力。对数据集DataSet的特点总结可以总结为四点:

n 第一,使用数据集对象DataSet无需与数据库直接交互;

n 第二,DataSet对象是存储从数据库检索到的数据的对象;

n 第三,DataSet对象是零个或多个表对象的集合,这些表对象由数据行和列、约束和有关表中数据关系的信息组成;

n 第四,DataSet对象既可容纳数据库的数据,也可以容纳非数据库的数据源。

2.DataSet的结构,常用属性及方法

数据集DataSet是以DataSet对象形式存在的。DataSet对象是一种用户对象,此对象表示一组相关表,在应用程序中这些表作为一个单元来引用。DataSet对象的常用属性是TablesRelations等。DataSet对象的层次结构图422所示:

<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
4-22 DataSet对象的层次结构图
DataSet对象由数据表及表关系组成,所以DataSet对象包含DataTable对象集合TablesDataRelation对象集合Relations。而每个数据表又包含行和列以及约束等结构,所以DataTable对象包含DataRow对象集合RowsDataColumn对象集合ColumnsConstraint对象集合ConstraintsDataSet层次结构中的类请参见表410所示:

4-10 DataSet层次结构中的类表

说明

DataTableCollection

包含特定数据集的所有DataTable对象

DataTable

表示数据集中的一个表

DataColumnCollection

表示DataTable对象的结构

DataRowCollection

表示DataTable对象中的实际数据行

DataColumn

表示DataTable对象中列的结构

DataRow

表示DataTable对象中的一个数据行

DataSet层次结构中各个类的关系,请参见图423所示:

4-23 DataSet层次结构中类之间关系图
423所示的是用一个具体实例来描述的DataSet层次结构中各个类之间的关系。整个图表示的是一个DataSet对象,用来描述一个学生成绩管理系统的客户端数据库。DataSet对象中的DataTableCollection数据表集合包含三个DataTable对象,分别是StudentTable代表学生表、ClassTable代表班级表和GradeTable代表成绩表。其中在StudentTable对象中的DataColumnCollection数据列集合包含四个DataColumn对象,分别是id代表学生号、name代表学生姓名、class代表学生班级号和sex代表学生性别。StudentTable对象还包含了按数据列定义结构的DataRow数据行集合DataRowCollectionDataRowCollection数据行集合中的每个DataRow数据行表示一个学生的数据信息。例如第一条,学号为“1”、姓名是“小菲”、班级为“5”、性别为“女”。DataSet对象常用的方法和属性请参见表411和表412所示:

4-11 DataSet对象常用属性表
属性

说明

DataSetName

用于获取或设置当前数据集的名称

Tables

用于检索数据集中包含的表集合

4-12 DataSet对象常用方法表
方法

说明

Clear()

清除数据集中包含的所有表的所有行

HasChanges()

返回一个布尔值,指示数据集是否更改了

3.数据集的工作原理

数据集DataSet的工作原理请参见图424所示:

4-24 数据集的工作原理图

424所示的过程就是数据集DataSet的工作原理。首先,客户端与数据库服务器端建立连接。然后,由客户端应用程序向数据库服务器发送数据请求。数据库服务器接到数据请求后,经检索选择出符合条件的数据,发送给客户端的数据集,这时连接可以断开。接下来,数据集以数据绑定控件或直接引用等形式将数据传递给客户端应用程序。如果客户端应用程序在运行过程中有数据发生变化,它会修改数据集里的数据。当应用程序运行到某一阶段时,比如应用程序需要保存数据,就可以再次建立客户端到数据库服务器端的连接,将数据集里的被修改数据提交给服务器,最后再次断开连接。

把这种不需要实时连接数据库的工作过程叫做面向非连接的数据访问。在DataSet对象中处理数据时,客户端应用程序仅仅是在本地机器上的内存中使用数据的副本。这缓解了数据库服务器和网络的压力,因为只有在首次获取数据和编辑完数据并将其回传到数据库时,才能连接到数据库服务器。

虽然这种面向非连接的数据结构有优点,但还是存在问题。当处于断开环境时,客户端应用程序并不知道其他客户端应用程序对数据库中原数据所做的改动。很有可能得到的是过时的信息。

4.案例学习:通过编码创建DataSet对象

    新建窗体命名为Form5.cs,双击Form5的窗体界面,进入后台编码区域,在其窗体加载初始化事件中键入如下代码:

private void Form5_Load(object sender, EventArgs e)

        {

            // 建立SQL Server数据库连接

            string connstring = "Data Source=(local);Initial Catalog=school;User ID=sa";

            SqlConnection connection = new SqlConnection(connstring);

            connection.Open();

            string sqlstring = "select * from student";

            SqlCommand mycom = new SqlCommand(sqlstring, connection);

            SqlDataAdapter adapter = new SqlDataAdapter();

            adapter.SelectCommand = mycom;

            // 创建DataSet对象

            DataSet SQLds = new DataSet();

            adapter.Fill(SQLds);// 通过SqlDataAdapter对象填充DataSet对象

            // 释放数据库连接资源。要养成了好的编程习惯,操作完数据后记住打扫垃圾!

            connection.Dispose();

            connection.Close();

            connection = null;

        }

1.数据集的类型

数据集的类型分为类型化数据集和非类型化数据集,关于对他们的描述请参见图425所示:

4-25 数据集的分类图

使用类型化数据集访问列,参见如下代码:

        string employeeName;

        employeeName = dsEmployees.Emp[0].EmpName;
dsEmployees数据集的Emp表中第一个记录返回EmpName列,然后将其存储在employeeName字符串变量中。使用非类型化数据集访问列 ,参见如下代码:

                
        string employeeName;

        employeeName = dsEmployees.Tables["Emp"].Rows[0]["EmpName"];
使用Tables集合返回EmpName列。

 
小知识:

typed DataSetuntyped DataSet

typed DataSet 是从DataSet派生的,它根据事先定义的Data Schema生成数据集,对数据集中的字段实行强类型约束。你可以通过它产生的cs文件看到许多方法对DataTable的操作进行了封装,这样你就可以通过MyDataSet.MyTable.Field对字段进行访问,而不是像DataSet那样:

MyDataSet.Tables["TableName"]["Field"]; 简化了编程,同时不容易出错,想象一下如果在"Field"中拼错了字段名,那么编译器也不会检查出来,对于typed DataSet就不用了,如果你Field写错的话,那么马上就可以知道。

还有就是如果你在Typed DataSet包含多数据集,同时在XSD中对这些数据集建立关系和约束,那么Type DataSet会生成相应的方法来反映这些关系和约束。如果使用untyped DataSet,你需要自己做。

性能上的考虑:虽然Typed DataSet创建对象实例的时候比unTypede DataSet要多一些开销(时间和空间),但是在填充数据的时候要比untyped DataSet快,这是因为DataAdapter已经知道怎么Fill一个Typed DataSet,相比之下,DataSet需要两次读取数据库,第一次取得数据库中表的结构信息,第二次才fill数据。

Typed DataSet 相对于DataSet的缺陷:除了创建的开销之外,Typed DataSet不如DataSet灵活,因为Typed DataSet一旦确定,数据表的结构就固定了,如果需要修改,必须重新生成。

DataSet你可以随时根据需要进行操作(比如添加字段,删除字段等)。

1. 案例学习:数据集的类型——通过图形化界面生成类型化数据集

通过图形化界面生成类型化数据集的过程大致分为三步:

u  实验步骤(1)

创建数据库连接对象。参见图426所示:

4-26 创建数据库连接对象图

从工具箱中选中SqlConnection,拖放到窗体上,窗体设计器的组件栏出现sqlConnection1对象,鼠标右击sqlConnection1对象,选择“属性”。在属性窗口找到ConnectionString属性,在右侧下拉栏里选择“新建连接”,弹出“添加连接”窗口。在“添加连接”窗口的服务器名处填写“(local)”,选择“使用SQL Server身份验证”,用户名为“sa”,选择或输入一个数据库名处选择“school”。最后点击“确定”关闭“添加连接”窗口。ConnectionString属性右侧栏中出现字符串“Data Source=(local);Initial Catalog=school;User ID=sa”。

前面这个过程可能因不同服务器、不同数据库、不同帐号而不同,这里只是举个例子,希望读者根据具体情况来完整这一步。

u 实验步骤(2)

添加新数据源。参见图427所示:

4-27 添加新数据源图

接上一步,在.net开发环境的工具栏里选择“数据”菜单中的“添加新数据源”,弹出“数据源配置向导”窗口。在这个窗口点击“数据库”图标,点击“下一步”,然后选择“schoolConnectionStringSettings)”。再连续点击两次“下一步”,接下来选择窗体中的表、视图、存储过程或者函数。最后点击“完成”关闭窗口。

u 实验步骤(3)

建立类型化数据集。参见图428所示:

4-28 建立类型化数据集图

接上一步,在工具箱中选择DataSet,拖放到窗体上,弹出“添加数据集”窗口,默认选择类型化数据集,名称为“WindowsApplication1.schoolDataSet”。这个名称源于数据源中的“schoolDataSet”。点击“确定”关闭“添加数据集”窗口,窗体设计器的组件栏出现schoolDataSet1对象。经过上述三步,在图形化界面就生成类型化数据集。

1. 案例学习:数据集的类型——通过图形化界面生成非类型化数据集

通过图形化界面生成非类型化数据集的过程大致也分为三步:

u 实验步骤(1)

建立表及主键。参见图429所示:

4-29 建立表及主键图

在工具箱中选择DataSet,拖放到窗体上,弹出“添加数据集”窗口,选择“非类型化数据集”。点击“确定”关闭“添加数据集”窗口,窗体设计器的组件栏出现dataSet1对象。鼠标右击dataSet1对象,选择“属性”。在属性窗口找到Tables属性,在右侧栏里选择“”按钮,弹出“表集合编辑器”窗口。在“表集合编辑器”窗口中点击“添加”按钮,左侧列表框中出现一个表名为“Table1”的项,这是添加的第一个表。“表集合编辑器”窗口的右侧列表的是表Table1的属性。找到TableName属性,将其设置为“student”。找到Columns属性,在右侧栏里选择“”按钮,弹出“列集合编辑器”窗口。“列集合编辑器”窗口与“表集合编辑器”窗口基本一样。用类似的办法连续点击五次“添加”按钮,左侧列表框中出现五项。在右侧属性栏,将其ColumnName属性分别设置为“sno”、“sname”、“ssex”、“sclass”、“birthday”,同时也修改DataType属性为相应的值。最后点击“关闭”关闭“列集合编辑器”窗口。

u  实验步骤(2)

建立表的主键唯一性约束。参见图430所示:

4-30 建立表的主键唯一性约束图

接上一步,找到表studentConstraints属性,在右侧栏里选择“”按钮,弹出“约束集合编辑器”窗口。“约束集合编辑器”窗口与“表集合编辑器”窗口基本一样。点击“添加”时要选择“唯一约束”或“外键约束”。选择“唯一约束”后,弹出“唯一约束”窗口,接下来就要填写约束名称,选择主键列“sno”,下面的“主键”复选框也要勾选。点击“确定”关闭“唯一约束”窗口。这时“约束集合编辑器”窗口中左侧列表框出现一个项,这是添加的第一个主键唯一性约束。

u 实验步骤(3)

建立表的外键约束。参见图431所示:

4-31 建立表的外键约束图

接上一步,在“约束集合编辑器”窗口点击“添加”时要选择“外键约束”,弹出“外键约束”窗口。在“外键约束”窗口填写约束名称,选择外键约束的父表(即被引用的表),以及父表被引用的键列和当前表的外键列。同时还要选择更新规则、删除规则和接受/拒绝规则。点击“确定”关闭“唯一约束”窗口。这时“约束集合编辑器”窗口中左侧列表框出现另一个项,这是添加的第一个外键约束。再连续点击两次“关闭”按钮,关闭“约束集合编辑器”窗口和“表集合编辑器”窗口。经过上述三步,在图形化界面就生成非类型化数据集。

 
小知识:

更新、删除规则的选择

更新、删除 以及接受/拒绝规则中可选项有四种:CascadeNoneSetNullSetDefault

其中Cascade表示级联操作,当父表键列发生 更新、删除 以及接受/拒绝操作时,子表的外键列也发生相应的操作。None表示无操作,当父表键列发生 更新、删除 以及接受/拒绝操作时,子表的外键列不采取任何操作。SetNull表示置空操作,当父表键列发生 更新、删除 操作时,子表的外键列相关行中的值置空。SetDefault表示置默认值操作,当父表键列发生 更新、删除 操作时,子表的外键列相关行中的值置DefaultValue属性中默认的值。

1. 案例学习:数据集的类型——编码实现非类型化数据集

在本节“通过编码创建DataSet对象”的案例学习中,我们建立了窗体Form5,本案例在Form5窗体之中添加一个Label标签控件,命名为Label1,用以显示从数据库表之中读取的相关数据信息,最终实现通过编码实现非类型化数据集的实验目的。

private void Form5_Load(object sender, EventArgs e)

{

string connstring = "Data Source=(local);Initial Catalog=school;User ID=sa";

            SqlConnection connection = new SqlConnection(connstring);

            connection.Open();

            string sqlstring = "select * from student";

            SqlCommand mycom = new SqlCommand(sqlstring, connection);

            SqlDataAdapter adapter = new SqlDataAdapter();

            adapter.SelectCommand = mycom;

            // 设置DataSet对象

            DataSet ds = new DataSet();

            adapter.Fill(ds);

            // 开始循环读取DataSet中的数据表信息

            for (int i = 0; i < ds.Tables[0].Rows.Count; i++)

            {

                //label1.Text += ds.Tables[0].Rows[i]["sname"].ToString()+"\n";

                label1.Text += ds.Tables[0].Rows[i][2].ToString() + "\n";

            }

            // 释放数据库连接资源

            connection.Dispose();

            connection.Close();

            connection = null;           

        }