ADO.NET 2.0技术内幕(1.2)

1.2  ADO.NET对象模型
在了解ADO.NET的用途以及它在整个Visual Studio 体系结构中所处的位置之后,现在来更深入地了解这一技术。本章将简要介绍ADO.NET对象模型,以及它与微软先前数据访问技术的区别。
设计ADO.NET的目的就是为了帮助开发人员开发在Intranet和Internet上使用的高效多层数据库应用程序,而且ADO.NET对象模型也提供了这样的手段。图1.1显示了包含ADO.NET对象模型的类。中间的虚线将对象模型分为两部分。虚线左边的对象是连接对象。这些对象直接与数据库通信,以管理连接和事务,以及从数据库检索数据和向数据库提交所做的更改。虚线右边的对象是非连接对象,允许用户脱机处理数据。
图1.1  ADO.NET 对象模型
由ADO.NET对象模型中非连接部分组成的对象不与连接对象直接通信。这是对微软先前数据访问对象模型的一个重要修改。在ADO中,Recordset对象存储查询的结果。可以调用其Open方法以提取查询结果,调用其Update(或UpdateBatch)方法,以向数据库提交存储在Recordset中的修改。
稍后讨论的ADO.NET DataSet(数据集)在功能上与ADO Recordset相当。但是DataSet不与数据库进行通信。为了从数据库提取数据并放入DataSet中,可以将DataSet传递给一个已连接ADO.NET对象(DataAdapter)的Fill方法。与此类似,为了向数据库提交存储在DataSet中的挂起更改,可以将DataSet传递给DataAdapter对象的Update方法。
1.2.1  .NET数据提供程序
.NET数据提供程序是一个类的集合,专门设计用来同特定类型的数据存储区进行通信。.NET Framework包括4种此类提供程序:SQL Client .NET数据提供程序、Oracle Client .NET数据提供程序、ODBC .NET数据提供程序和OLE DB .NET数据提供程序。SQL Client和Oracle Client .NET数据提供程序设计用于分别同特定数据库——SQL Server和Oracle进行通信。ODBC和OLE DB .NET数据提供程序经常被称为“桥梁”组件,因为它们用作通向早期技术——ODBC和OLE DB的桥梁。这些提供程序使开发人员能够分别通过ODBC驱动程序和OLE DB提供程序同各种数据存储区进行通信。
每种.NET数据提供程序都实现相同的基类—— ProviderFactory,Connection,ConnectionStringBuilder,Command,DataReader,Parameter和Transaction,只是其实际名称取决于该数据提供程序。例如,SQL Client .NET数据提供程序具有SqlConnection类,而ODBC .NET数据提供程序包括OdbcConnection类。无论使用哪种.NET数据提供程序,此数据提供程序的Connection类都通过相同的基接口实现相同的基本特性。要对数据存储区打开一个连接,可创建此提供程序连接类的一个实例,设置此对象的ConnectionString属性,然后调用其Open方法即可。
每个.NET数据提供程序都有自己的命名空间。.NET Framework中所包括的4个提供程序是System.Data命名空间的一个子集,非连接对象就位于System .Data命名空间之中。SQL Client数据提供程序位于System.Data.SqlClient命名空间中,ODBC .NET数据提供程序位于System.Data.Odbc命名空间中;OLE DB .NET数据提供程序位于System.Data.OleDb命名空间中;Oracle Client .NET数据提供程序则位于System.Data.OracleClient命名空间中。
命名空间
命名空间是多个对象的逻辑分组。.NET Framework很大,所以为了更容易地利用.NET Framework开发应用程序,微软将对象划分到不同的命名空间中。图1.2显示了.NET Framework命名空间层次结构的一部分。
使用命名空间的最重要原因在于防止程序集中出现名称冲突。在利用不同的命名空间时,如果开发人员处理的不同组件将被合并为单一解决方案,那么可以为不同项使用相同名称。因为这些名称是分离的,所以它们在编译时不会相互影响。使用命名空间的一个更实际原因在于:对象分组后更易于查找。有时,人们在查找类时可能忘记类的确
切名称。如果.NET Framework中的类没有被划分为较小的命名空间,就需要在框架的所有类中,按字母表顺序寻找所需要的类。幸运的是,人们一般会记得所需类的命名空间。在命名空间中寻找类要简单一些,这是因为要查看的类数目少得多。
图1.2  .NET Framework中的命名空间
如需了解有关使用.NET Framework或Visual Studio中命名空间的详细信息,请参阅.NET Framework SDK。
因为每种.NET数据提供程序都实现相同的基本特性,所以无论使用哪种提供程序,所编写的代码看起来都有些相似。在以下代码段中可以看到,要从使用ODBC .NET数据提供程序转换为使用SQL Client .NET数据提供程序,要完成的全部工作就是改变要实例化的类和连接字符串的内容,使其符合数据提供程序的标准。
Visual Basic
'打开和关闭一个OdbcConnection
Dim cnOdbc As New OdbcConnection
cnOdbc.ConnectionString = "Driver={SQL Server};" & _
                              "Server=./SQLExpress;" & _
                              "Database=Northwind;..."
cnOdbc.Open()
...
cnOdbc.Close()
'Open and close a SqlConnection
Dim cnSql As New SqlConnection
cnSql.ConnectionString = "Data Source=./SQLExpress;" & _
                             "Initial Catalog=Northwind;..."
cnSql.Open()
...
cnSql.Close()
Visual C#
//打开和关闭一个OdbcConnection
OdbcConnection cnOdbc = new OdbcConnection();
cnOdbc.ConnectionString = "Driver={SQL Server};" +
                              @"Server=./SQLExpress" +
                              "Database=Northwind;...";
cnOleDb.Open();
...
cnOleDb.Close();
//打开和关闭一个SqlConnection
SqlConnection cnSql = new SqlConnection();
cnSql.ConnectionString = @"Data Source=./SQLExpress;" +
                              "Initial Catalog=Northwind;...";
cnSql.Open();
...
cnSql.Close();
1.2.2  为什么使用分离的类和库
微软过去的数据访问技术都没有为不同数据存储区使用相互分离的库和类。许多开发人员都会提出这样的问题:微软为什么要做出这样重大的变更?主要原因有3个:性能、扩展性和增殖性(proliferation)。
1. 性能更佳
迁移到.NET数据提供程序是如何提高性能的?在编写ADO代码时,实际上是将ADO接口用作与数据存储区进行通信时的“中介”。开发人员告诉ADO自己希望使用哪个提供程序,而ADO将开发人员的调用传递给适当的数据提供程序。然后该提供程序就会执行所请求的操作,并通过ADO库返回结果。
.NET数据提供程序不涉及中间层。开发人员直接与数据提供程序通信,该数据提供程序使用数据存储区的低级编程接口与数据存储区通信。使用ADO.NET的SQL Client .NET数据提供程序与SQL Server通信要快于使用ADO和SQL Server OLE DB提供程序,原因就在于少涉及一层。
2. 扩展性更强
在SQL Server 2000引入XML特性时,ADO开发小组就面临着一项很有意义的挑战。为了给ADO添加能使开发人员从SQL Server 2000中检索XML数据的特性,ADO开发小组就必须为OLE DB API以及SQL Server OLE DB提供程序添加新的接口。
.NET数据提供程序更易扩展。它们只需要支持相同的基础接口,并可以在适当的时候提供额外的提供程序专用功能。OLE DB .NET数据提供程序的Command对象所公开的全部方法和属性,SQL Client .NET数据提供程序的Command对象(SqlCommand)也均予以公开,此外还添加了一个方法,以获得以XML方式返回的查询结果。
SQL Server 2005包含一组新特性,例如,能够使应用程序利用通知服务,以便在服务器上的查询结果发生变化时收到通知。微软并没有改变ADO.NET公共类的内部工作,而只是向SQL Client .NET数据提供程序中提供了两个新类,以充分利用这些新的SQL Server特性。
3. 增殖性
1998年7月,微软第一次随Microsoft Data Access Components 2.0 (微软数据访问组件,MDAC)配套发售用于SQL Server、Microsoft Access和Oracle的OLE DB提供程序。微软和其他开发小组都创建了本地OLE DB提供程序,以与其他数据存储区进行通信,但并没有很多OLE DB提供程序可供使用。如果一位开发人员正在使用ADO,但未使用微软的OLE DB提供程序,那么他使用的很有可能是ODBC (OLE DB的前身)驱动程序。还有很多ODBC驱动程序可供使用,主要是因为它们易于开发。许多开发人员都发现很难开发自己的OLE DB提供程序。
通过对比,可以发现.NET数据提供程序的编写比较简单,需要实现的接口要少得多。微软简化了为ADO.NET开发提供程序的过程,从而使开发人员可以更容易开发.NET数据提供程序。.NET数据提供程序的数量越多,可以通过ADO.NET访问的不同数据源就越多。
上面两段文字是为本书第1版所写的。从那时开始,数据库供应商、独立软件供应商和各种开源项目都开发了.NET数据提供程序。开发新版本OLE DB提供程序的小组越来越少。在编写本书第2版时,大多数利用微软开发技术开发组件的小组都专注于开发.NET数据提供程序和ODBC驱动程序中的一种或两种。
1.2.3  本书中对.NET数据提供程序的介绍
由于每个.NET数据提供程序都实现相同的基本接口,所以在本书中不需要针对所有.NET数据提供程序来解释如何使用这些接口。本书将着重介绍一种数据提供程序:SQL Client .NET数据提供程序。在本书第1版中,主要介绍了OLE DB .NET数据提供程序,但OLE DB提供程序已经过时,OLE DB .NET数据提供程序同样已经过时。另外SQL Client .NET数据提供程序提供了大量新特性——异步查询操作、批量更新、查询通知以及批量复制等。
附录A将介绍其他三种.NET数据提供程序的特性以及公共提供程序模型。第12章将使用SQL XML .NET数据提供程序来演示如何使用ADO.NET的某些XML特性。由于SQL XML .NET数据提供程序没有提供新特性,而且省略了其他.NET数据提供程序中所包含的许多类,所以通常认为它不是一种完备的.NET数据提供程序。
在讨论所有.NET数据提供程序共有的某个类时,用于指代该类的名称中通常不包含提供程序名称,例如使用DataAdapter,而不是SqlDataAdapter或OdbcDataAdapter。
1.2.4  连接对象
ADO.NET对象模型包括用于直接与数据源通信的类。本书将这样的类(如图1.1所示,在虚线左侧)称为ADO.NET的“连接”类。多数连接类表示基本的数据访问概念,如与数据库、查询以及查询结果的物理连接等。
1. ProviderFactory类
ProviderFactory类是ADO.NET 2.0中新添加的类,相当于一个对象工厂,使开发人员能够为.NET数据提供程序创建其他类的实例。每个ProviderFactory类都提供一种Create方法,此方法创建Connections,ConnectionStringBuilders,Commands,Parameters,DataAdapters和CommandBuilders。
2. Connection类
Connection对象表示与数据源之间的连接。可通过Connection类的各种不同属性(property)指定数据源的类型、位置以及其他属性(attribute)。Connection对象大致相当于ADO Connection对象或DAO Database对象,可用它来建立或断开与数据库的连接。Connection对象起到渠道的作用,其他对象如DataAdapter和Command对象通过它与数据库通信,以提交查询并获取查询结果。
3. ConnectionStringBuilder类
ConnectionStringBuilder类是ADO.NET 2.0中新添加的类,它简化了为.NET数据提供程序建立连接字符串的过程。每个ConnectionStringBuilder类公开一些属性,这些属性对应于可在.NET数据提供程序的连接字符串中使用的选项。例如,OdbcConnectionStringBuilder类公开一个Driver属性,OleDbConnectionStringBuilder类公开一个Provider属性。在使用ConnectionStringBuilder建立连接字符串之后,就可以利用ConnectionStringBuilder对象的ConnectionString属性来访问该连接字符串。
4. Command类
Command对象的结构类似于ADO Command或DAO QueryDef对象。Command对象可表示对数据库的查询、对存储过程的调用或返回特定表内容的直接请求。
数据库支持多种不同类型的查询。有些查询通过引用一个或多个表、视图或者是通过调用一个存储过程来获取数据行,有些查询会对数据行进行修改,还有一些查询通过创建或修改诸如表、视图或存储过程等对象来对数据库的结构进行有关操作。可使用Command对象对数据库执行任何一种查询操作。
使用Command对象查询数据库相当简单。先将Connection属性设置为连接数据库的Connection对象,然后在CommandText属性中指定查询文本。可以提供一个如下所示的标准SQL查询:
SELECT CustomerID, CompanyName, ContactName, Phone FROM Customers
还可以仅提供表、视图或存储过程的名称,并使用Command对象的CommandType属性来设置需要执行的查询类型。Command类提供了执行查询的不同方式。如果此查询不返回数据行,调用ExecuteNonQuery方法即可。Command类还有一个ExcuteReader方法,该方法返回一个DataReader对象,该对象可用来检查由查询所返回的数据行。如果只希望检索该查询所返回的第一行第一列,可以通过调用Command对象的ExecuteScalar方法来节省一些代码行。SqlCommand包括第4个执行方法ExecuteXmlReader,它与ExcuteReader相似,但专门用于处理以XML格式返回结果的查询。
5. DataReader类
DataReader用于以最快的速度检索并检查查询所返回的行。可使用DataReader对象来检查查询结果,一次检查一行。当移向下一行时,前一行的内容就会被放弃。DataReader不支持更新操作。由DataReader返回的数据是只读的。由于DataReader对象支持最小特性集,所以它的速度非常快,而且是轻量级的。
如果曾经用过先前数据访问技术中的游标,那么可以将DataReader看作一种仅前向型(forward-only)只读游标,或者流水(firehose)游标。
6. Transaction类
有时可能希望将对数据库所做的更改组织起来,将它们看作一个独立的工作单元。在数据库编程中,这样的工作单元就称为事务(transaction)。假设某数据库包含银行信息,还有支票账户表和储蓄账户表,一位用户想要将钱从储蓄账户转到支票账户中去。在所编写的代码中,希望确保储蓄账户的提款操作和支票账户的存储操作为一个独立单元,要么同时成功完成,要么两项操作都不发生。这里就可使用事务来达到这一目的。
Connection对象有一个BeginTransaction方法,可用来创建Transaction对象。在Transaction对象的生存期内,可以使用该对象来提交或取消对数据库所做的更改。在上面的银行示例中,对储蓄账户和支票账户所做的更改都会被包含在一个事务中,因此将会作为一个工作单元被提交或取消。
7. Parameter类
假设希望查询Orders表以获得某特定客户的所有订单。所用查询可能如下所示:
SELECT CustomerID, CompanyName, CompanyName, Phone FROM Customers
     WHERE CustomerID = 'ALFKI'
在查询的WHERE子句中,为CustomerID列所使用的值取决于您需要查看哪家客户的订单。不过,如果使用这种类型的查询,以后每次要查看不同客户的订单时,就必须修改该查询的文本。
为简化执行此类查询的过程,可以用一个参数标记(marker)替换CustomerID的值,如下面的查询所示:
SELECT CustomerID, CompanyName, CompanyName, Phone FROM Customers
     WHERE CustomerID = @CustomerID
然后,在执行此查询之前给参数提供一个值。许多开发人员对参数化查询的依赖性很强,这是因为参数化查询有助于简化编程,并生成效率更高的代码。
要使用参数化的Command对象,可先为查询中的每个参数创建Parameter对象,并将它们添加到Command对象的Parameter集合中。ADO.NET的Parameter对象公开一些属性和方法,可用来定义参数的数据类型和值。要使用那些通过输出参数返回数据的存储过程,可将Parameter对象的Direction属性设置为ParameterDirection枚举中的适当值。
8. DataAdapter类
DataAdapter类代表了微软数据访问模型的一个新概念。DataAdapter类在ADO和DAO中并没有真正的对应类,当然,可以认为ADO Command 对象和DAO QueryDef对象与DataAdapter对象多少有一些联系,它们曾被删除过。
DataAdapter对象充当数据库和ADO.NET对象模型中非连接对象之间的桥梁。DataAdapter对象类的Fill方法提供了一种高效机制,用于将查询结果引入DataSet或DataTable中,以便能够脱机处理数据。还可以利用DataAdapter对象向数据库提交存储在DataSet对象中的挂起更改。
ADO.NET DataAdapter类公开了大量属性,这些属性实际上是Command对象。例如,SelectCommand属性包含一个Command对象,该对象表示将用来填充DataSet对象的查询。DataAdapter类还有UpdateCommand,InsertCommand和DeleteCommand等属性,它们分别对应于用来向数据库提交已修改数据行、新建数据行或被删除数据行的Command对象。
这些Command对象提供了更新功能,在ADO和DAO的Recordset对象中,这些更新自动进行。例如,当在ADO中运行一个查询以生成一个Recordset对象时,ADO的游标引擎就会询问数据库中有关此查询的元数据,以确定结果来自哪里。然后ADO会使用该元数据建立更新逻辑,以将Recordset对象中的更改转换为数据库中的更改。
那么ADO.NET的DataAdapter对象为什么拥有单独的UpdateCommand,InsertCommand和DeleteCommand属性呢?这是为了允许开发人员定义自己的更新逻辑。ADO和DAO的更新功能都十分有限,因为这两种对象模型都将Recordset中的更改转换为对数据库中的表进行直接引用的操作查询。为了维护数据的安全性和完整性,许多数据库管理员都限制对其数据库中表的访问,因此更改表内容的唯一途径就是调用存储过程。ADO和DAO不知道如何使用存储过程提交更改,也没有提供可让开发人员指定自己更新逻辑的机制。ADO.NET DataAdapter则可以。
利用DataAdapter对象,可以设置UpdateCommand,InsertCommand以及DeleteCommand属性来调用存储过程,这些存储过程将修改、添加或删除数据库中相应表的数据行。然后可以只调用DataAdapter对象的Update方法,ADO.NET就会使用所创建的Command对象向数据库提交DataSet中缓存的更改。
如前文所述,DataAdapter类会填充DataSet对象中的表,而且能读取缓存的更改并将其提交给数据库。DataAdapter有一些支持属性,可用来跟踪在什么位置发生了什么操作。TableMappings集合就是其中的一个属性,它用于跟踪数据库中的哪个表与DataSet对象中的哪个表相对应。每个表映射都有一个用于映射列的类似属性,称为ColumnMapping集合。
1.2.5  非连接对象
前面介绍了可使用.NET数据提供程序中的连接类来连接数据源、提交查询以及查看查询结果。但是这些连接类只允许将数据作为仅前向型的只读数据流进行查看。如果要对查询结果进行排序、搜索、筛选或修改时,应当怎么做呢?
ADO.NET对象模型包括了提供这种功能的类。这些类充当脱机数据缓存。将查询结果导入DataTable之后(稍后将进行介绍),就可以关闭与数据源的连接,并继续使用数据。如上所述,因为这些对象不需要与数据源进行活动连接,所以将其称为“非连接”对象。
下面介绍ADO.NET对象模型中的非连接对象。
1. DataTable类
ADO.NET的DataTable对象与ADO和DAO中的Recordset对象相似。DataTable对象允许通过行和列的集合查看数据。可以通过调用DataAdapter对象的Fill方法将查询结果存储在DataTable中,如下面的代码片段所示。
Visual Basic
Dim strConn, strSQL As String
strConn = "Data Source=./SQLExpress;" & _
            "Initial Catalog=Northwind;Integrated Security=True;"
strSQL = "SELECT CustomerID, CompanyName FROM Customers"
Dim da As New SqlDataAdapter(strSQL, strConn)
Dim tbl As New DataTable()
da.Fill(tbl)
Visual C#
string strConn, strSQL;
strConn = @"Data Source=./SQLExpress;" +
              "Initial Catalog=Northwind;Integrated Security=True;";
strSQL = "SELECT CustomerID, CompanyName FROM Customers";
SqlDataAdapter da = new SqlDataAdapter(strSQL, strConn);
DataTable tbl = new DataTable();
da.Fill(tbl);
在从数据库中读出数据并将其存储于DataTable对象之后,该数据即从服务器断开连接。然后就可以查看DataTable对象的内容,而不会在ADO.NET和数据库之间产生任何网络通信流量。由于采用脱机方式处理数据,所以不再需要保持与数据库之间的活动连接。但请切记:运行查询之后,将无法看到其他用户对数据库所做出的修改。
DataTable类包含了其他非连接对象的集合,稍后将对此进行说明。可以通过DataTable的Rows属性访问其内容,这一操作会返回DataRow对象的一个集合。如果希望查看DataTable的结构,可以使用其Columns属性来获取DataColumn对象的集合。DataTable还允许为该类中存储的数据定义一些约束,如主键。可以通过DataTable对象的Constraints属性访问这些约束。
2. DataColumn类
每个DataTable都有一个Columns集合,该集合是DataColumn对象的容器。从其名称可以看出,一个DataColumn对象对应表中的一列。然而,DataColumn对象并非实际包含存储在DataTable中的数据,而是存储了有关该列结构的信息。这种类型的信息——有关数据的数据——就称为元数据。例如,DataColumn中公开一个DataType属性,该属性描述了该列所存储的数据类型(如字符型或整型)。DataColumn类还有其他一些属性,如ReadOnly,AllowDBNull,Unique,Default以及AutoIncrement等,这些属性可帮助您控制列中的数据能否更新、限制可以在列中存储的内容,或是规定如何为新数据行生成值。
DataColumn类还有一个Expression属性,可以用来指定如何计算列中的数据。通常的做法是根据表达式来计算查询中的一个列值,而不是根据数据库中一个表的列内容进行计算。例如,在示例Northwind数据库中(大多数与数据库相关的微软产品中都带有这一数据库),Order Details表中的每一行都包含UnitPrice和Quantity列。通常,如果希望查看数据结构中该订单条目的总成本,就需要为此查询添加一个计算列。下面的SQL示例定义了一个名为ItemTotal的计算列。
SELECT OrderID, ProductID, Quantity, UnitPrice,
        Quantity * UnitPrice AS ItemTotal
    FROM [Order Details]
这一技术的缺点就在于数据库引擎只在查询时执行计算。如果修改了DataTable对象中UnitPrice或Quantity列的内容,ItemTotal列不会发生任何变化。
ADO.NET的DataColumn类定义了一个Expression属性来更完美地处理这种情景。当基于一个表达式来检查DataColumn对象的值时,ADO.NET会计算该表达式,并返回一个最新计算值。这样,如果更新了表达式中任一列的值,存储在计算列中的值都是准确的。下面的两个代码段说明了Expression属性的使用。
Visual Basic
Dim col As New DataColumn()
...
With col
    .ColumnName = "ItemTotal"
    .DataType = GetType(Decimal)
    .Expression = "UnitPrice * Quantity"
End With
Visual C#
DataColumn col = new DataColumn();
col.ColumnName = "ItemTotal";
col.DataType = typeof(Decimal);
col.Expression = "UnitPrice * Quantity";
Columns集合和DataColumn对象大致类似于ADO和DAO的Fields集合与Field对象。
3. Constraint类
DataTable类还提供了一种方式,用于对DataTable对象中本地存储的数据设置约束。例如,可以建立一个Constraint对象,确保某一列或多列中的值在DataTable中是唯一的。Constraint对象存在于DataTable对象的Constraints集合中。
4. DataRow类
要想访问存储在DataTable对象中的实际值,可以使用此对象的Rows集合,该集合中包含一组DataRow对象。要想查看存储在特定行、特定列中的数据,可以使用适当DataRow对象的Item属性来读取该行中任意列的值。DataRow类提供了Item属性的一些重载定义,可以通过向DataRow对象的Item属性传递列名称、索引值或相关联的DataColumn对象来指定可要查看哪一列。由于Item是DataRow类的默认属性,所以可以隐式地使用它,如下面的代码段所示。
Visual Basic
Dim row As DataRow
row = tbl.Rows(0)
Console.WriteLine(row(0))
Console.WriteLine(row("CustomerID"))
Console.WriteLine(row(tbl.Columns("CustomerID")))
Visual C#
DataRow row;
row = tbl.Rows[0];
Console.WriteLine(row[0]);
Console.WriteLine(row["CustomerID"]);
Console.WriteLine(row[MyTable.Columns["CustomerID"]]);
DataTable通过DataRows集合使所有数据行都可用,而不是仅为当前行返回数据。这是与ADO和DAO Recordset对象的一个明显差异,ADO和DAO的Recordset对象一次仅公开一行数据,因此需要使用诸如MoveNext等方法来浏览其内容。下面的代码段是在ADO Recordset内容中进行循环的一个例子。
Visual Basic的“经典方法”
Dim strConn As String, strSQL As String
Dim rs As ADODB.Recordset
strConn = "Provider=SQLOLEDB;Data Source=./SQLExpress;" & _
            "Initial Catalog=Northwind;Integrated Security=SSPI;"
strSQL = "SELECT CustomerID, CompanyName FROM Customers"
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open strSQL, strConn, adOpenStatic, adLockReadOnly, adCmdText
Do While Not rs.EOF
    Debug.Print rs("CustomerID")
    rs.MoveNext
Loop
为查看ADO.NET的DataTable内容,需要在DataTable对象Row属性中所包含的DataRow对象中进行循环,如下所示:
Visual Basic
Dim strSQL, strConn As String
strConn = "Data Source=./SQLExpress;" & _
            "Initial Catalog=Northwind;Integrated Security=True;"
strSQL = "SELECT CustomerID, CompanyName FROM Customers"
Dim da As New SqlDataAdapter(strSQL, strConn)
Dim tbl As New DataTable()
da.Fill(tbl)
For Each row As DataRow In tbl.Rows
    Console.WriteLine(row("CustomerID"))
Next row
Visual C#
string strSQL, strConn;
strConn = @"Data Source=./SQLExpress;" +
             "Initial Catalog=Northwind;Integrated Security=True;";
strSQL = "SELECT CustomerID, CompanyName FROM Customers";
SqlDataAdapter da = new SqlDataAdapter(strSQL, strConn);
DataTable tbl = new DataTable();
da.Fill(tbl);
foreach (DataRow row in tbl.Rows)
    Console.WriteLine(row["CustomerID"]);
DataRow对象也是进行更新的起始点。例如,可以调用DataRow对象的BeginEdit方法,通过Item属性修改此行中一些列的值,然后调用EndEdit方法将更改保存到该行中。通过调用DataRow对象的CancelEdit方法,可以取消在当前编辑会话中所做的修改。DataRow对象还公开了一些方法,用于从DataTable的DataRows对象集合中删除或移去某条目。
在改变了一行的内容时,DataRow对象会缓存这些更改,从而可以在以后向数据库提交它们。因此,在对一行中一列的值进行更改时,DataRow会保持该列中的原始值和当前值,以便成功地更新数据库。DataRow对象的Item属性还允许在某行存在挂起更改时检查某一列的原始值。
5. DataSet类
从其名称可以看出,DataSet对象包含一个数据集。可以将DataSet对象视为许多DataTable对象(存储在DataSet对象的Tables集合中)的容器。请切记,创建ADO.NET的目的是帮助开发人员建立大型的多层数据库应用程序。有时,开发人员可能希望访问一个运行在中间层服务器上的组件,以获取许多表的内容。这时不必重复调用该服务器以便每次从一个表中获取数据,而是可以将所有数据都封装入一个DataSet对象之中,并在一次单独调用中将其返回。但DataSet对象的功能绝不仅仅是作为多个DataTable对象的容器。
存储在DataSet对象中的数据未与数据库连接。对数据所做的任何更改都将只是缓存在每个DataRow之中。要将这些更改传递给数据库时,将整个DataSet回传给中间层服务器可能并非一种有效方法。可以使用GetChanges方法仅从DataSet中选出被修改的行。通过这样的方式,可以在不同进程或服务器之间传递较少数据。
DataSet还公开了Merge方法,该方法可作为GetChanges方法的一个补充。用于向数据库提交更改的中间层服务器(它使用的是由Merge方法返回的较小DataSet)将会返回一个包含着新获得数据的DataSet。可以使用DataSet类的Merge方法来将两个DataSet对象的内容合并入一个DataSet之中。这个例子又一次表明在设计开发ADO.NET时,一直将多层应用程序作为考虑内容之一。过去的微软数据访问模型就没有类似特性。
您可以创建DataSet对象,而且无须与数据库通信就可以用信息填充DataSet对象的Tables集合。在以前的数据访问模型中,在本地添加新行之前,通常需要查询数据库,然后将它们提交给数据库。而使用ADO.NET,在准备好提交新数据行之后才需要与数据库                通信。
DataSet类还有一些特性,利用这些特性可以将DataSet类写入一个文件或一个内存区域,或者从文件或内存区域中读出该类。您可以只保存DataSet对象的内容,或只保存DataSet对象的结构,也可以两者都保存。ADO.NET将此数据存储为XML文档。由于ADO.NET与XML密切相关,所以在ADO.NET的DataSet对象和XML文档之间传递数据只是小事一桩。这样就能够利用XML的强大功能之一:轻松转换数据结构的功能。例如,可以使用可扩展样式语言(Extensible Stylesheet Language,XSL)转换模板将输出到XML文档的数据转换为HTML。
6. DataRelation类
数据库中的表通常以某种方式相关联。例如,在Northwind数据库中,Orders表中的每个表项都与Customers表中的一个表项相关联,因此可以确定哪位客户下了哪些订单。您可能希望在应用程序中使用来自多个表的相关数据。ADO.NET的DataSet对象借助于DataRelation类来处理来自相关DataTable对象的数据。
DataSet类公开了一个Relations属性,该属性是DataRelation对象的一个集合。可以使用DataRelation对象来表明DataSet中不同DataTable对象之间的关系。在创建了DataRelation对象之后,就可以使用诸如以下代码来获取一个DataRow对象的数组,以获取与特定客户相关的订单。
Visual Basic
Dim ds As DataSet
Dim tblCustomers, tblOrders As DataTable
Dim rel As DataRelation
'以下是创建DataSet的代码
rel = ds.Relations.Add("Customers_Orders", _
                          tblCustomers.Columns("CustomerID"), _
                          tblOrders.Columns("CustomerID"))
For Each rowCustomer As DataRow In tblCustomers.Rows
    Console.WriteLine(rowCustomer("CompanyName"))
    For Each rowOrder As DataRow In rowCustomer.GetChildRows(rel)
        Console.WriteLine(" {0}", rowOrder("OrderID"))
    Next rowOrder
    Console.WriteLine()
Next rowCustomer
Visual C#
DataSet ds;
DataTable tblCustomers, tblOrders;
DataRelation rel;
//创建并初始化DataSet
rel = ds.Relations.Add("Customers_Orders",
                           tblCustomers.Columns["CustomerID"],
                           tblOrders.Columns["CustomerID"]);
foreach (DataRow rowCustomer in tblCustomers.Rows) {
    Console.WriteLine(rowCustomer["CompanyName"]);
    foreach (DataRow rowOrder in rowCustomer.GetChildRows(rel))
        Console.WriteLine(" {0}", rowOrder["OrderID"]);
    Console.WriteLine();
}
DataRelation对象还公开了一些属性,用来加强引用的完整性。例如,可以对一个DataRelation对象进行设置,使得在父行中对主键值进行修改时,更改会自动向下层叠到子行中。还可以对DataRelation对象进行设置,使得在删除DataTable中的一行时,所有子DataTable对象(由关联定义)中的相应行也会自动被删除。
7. DataView类
在将一查询结果置于DataTable对象中之后,就可以使用DataView对象以不同方式查看数据。如果希望根据某一列对DataTable对象的内容进行排序,只要将DataView的Sort属性设置为该列的名称即可。还可以设置DataView的Filter属性,使得只有符合特定标准的行可见。
可以使用多个DataView对象同时查看同一DataTAble。例如,在一个窗体中可以拥有两个表格,其中一个用于按字母顺序显示所有客户,另一个则用于显示按另一个字段(如国家或地区)排序的行。为了显示所有视图,需要将每个表格绑定到不同的DataView对象,但是这两个DataView都引用同一个DataTable。该属性使您不必以不同结构方式保留数据的两份副本。有关内容将在第8章详细讨论。
1.2.6  元数据
ADO和DAO允许基于查询返回的结果创建Recordset。数据访问引擎会检查结果集中的数据列,并根据该信息填充Recordset对象的Fields集合,设置名称、数据类型等。
ADO.NET为开发人员提供了两种选择。可以使用少数代码行,让ADO.NET自动检查结果的结构;也可以使用较多代码,其中包含了有关查询结果结构的元数据。
那么为什么要选择需要编写较多代码的选项呢?最主要的优点就在于其功能性更强、性能更佳。但是代码较多时又怎么会使应用程序的运行速度更快呢?这似乎有悖于人们的直觉感受,不是吗?
除非您正编写一个专用查询工具,否则您通常事先就知道查询结果的结构形式。例如,大多数ADO代码形式都与下例相似。
Dim rs as Recordset
'在此处声明其他变量
'初始化变量并与数据库建立连接
rs.Open strSQL, cnDatabase, adOpenStatic, adLockOptimistic, adCmdText
Do While Not rs.EOF
    List1.AddItem rs.Fields("UserName").Value
    rs.MoveNext
Loop
在此代码段中,编程人员知道该查询包含一个名为UserName的列。关键就在于一个开发人员通常都知道查询会返回哪些列,以及这些列使用何种数据类型。但是ADO并不能事先了解查询结果的形式。结果,ADO必须对OLE DB提供程序进行查询,提出诸如“查询的结果中有多少列?”,“这些列中每一列的数据类型是什么?”,“这些数据来自何方?”和“该查询中所引用的每个表的主键字段是什么?”等问题。OLE DB提供程序可以回答这些问题中的一部分,但是很多时候它都必须回调数据库。
为获取查询结果,并且将该数据存储在DataSet对象中,ADO.NET需要知道此类问题的答案。您可以自己提供这些信息,也可以强制ADO.NET从提供程序获取信息。当选择自己提供信息时,代码的运行速度就能加快,这是因为:与通过代码提供元数据相比,在运行时向提供程序询问此信息会使性能大幅降低。
尽管通过编写代码来准备DataSet的结构可以提高应用程序的性能,但编写代码可能非常沉闷乏味。幸运的是,Visual Studio包含了设计时数据访问特性,这些特性为我们综合了两者最优秀的性能。例如,您可以创建一个基于查询、表名称或存储过程的DataSet对象,然后配置向导就会生成ADO.NET代码,来运行此查询,并支持将更新提交给数据库。在下面的章节中将会详细讨论许多此类Visual Studio特性。
1.2.7  强类型DataSet类
Visual Studio还会生成强类型DataSet,以帮助开发人员简化数据库访问应用程序的开发过程。假设有一个名为Orders的简单表,其中包含两列:CustomerID和CompanyName。开发人员不必编写下面的代码。
Visual Basic
Dim ds As DataSet
'创建并填充DataSet
Console.WriteLine(ds.Tables("Customers").Rows(0)("CustomerID"))
Visual C#
DataSet ds;
//创建并填充DataSet
Console.WriteLine(ds.Tables["Customers"].Rows[0]["CustomerID"]);
而是可以编写以下代码:
Visual Basic
Dim ds As CustomersDataSet
'创建并填充DataSet
Console.WriteLine(ds.Customers(0).CustomerID)
Visual C#
CustomersDataSet ds;
//创建并填充DataSet
Console.WriteLine(ds.Customers[0].CustomerID);
强类型DataSet就是Visual Studio创建的一种类,可以通过属性使用其全部的表和列的信息。强类型DataSet对象还公开了一些用于创建新行等功能的定制方法。所以代码可以如下所示:
Visual Basic
Dim ds as DataSet
'生成DataSet和Customers DataTable的代码
Dim rowNewCustomer As DataRow
rowNewCustomer = ds.Tables("Customers").NewRow()
rowNewCustomer("CustomerID") = "ALFKI"
rowNewCustomer("CompanyName") = "Alfreds Futterkiste"
ds.Tables("Customers").Rows.Add(rowNewCustomer)
Visual C#
DataSet ds;
//生成DataSet和Customers DataTable的代码
DataRow rowNewCustomer;
rowNewCustomer = ds.Tables["Customers"].NewRow();
rowNewCustomer["CustomerID"] = "ALFKI";
rowNewCustomer["CompanyName"] = "Alfreds Futterkiste";
ds.Tables["Customers"].Rows.Add(rowNewCustomer);
可以用一行代码向表中创建并添加一个新行,如下所示:
ds.Customers.AddCustomersRow("ALFKI", "Alfreds Futterkiste")
在第9章将更深入地研究强类型DataSet对象。
来自: http://book.csdn.net/bookfiles/396/10039614586.shtml

你可能感兴趣的:(ADO.NET 2.0技术内幕(1.2))