上图显示了ADO.NET对象模型中的类。左边的是连接对象,这些对象直接与数据库通信,以管理连接和事务,以及从数据库检索数据和向数据库提交所作的更改。右边的是非连接对象,允许用户脱机处理数据。下面我们逐个了解这些类的作用
一、连接对象
1、.NET数据提供程序
.NET数据提供程序是一个类的集合,专门设计用来同特定类型的数据存储区进行通信。在.NET Framework中,包含了四种此类提供程序:SQL Client.NET数据提供程序、Oracle Client.NET数据提供程序、ODBC.NET数据提供程序和OLE DB.NET数据提供程序。
每种.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命名空间中。
为什么微软要用分离的类和库?
过去微软使用的ADO技术,并没有针对不同的数据提供程序,提供不同的类,为什么在ADO.NET中要这么做,主要出于性能、扩展性的考虑:
- 性能更佳
在使用ADO技术时,实际上是将ADO接口用作于数据存储区进行通信时的“中介”。开发人员告诉ADO,自己希望使用哪个提供程序,而ADO将开发人员的调用传递给适当的数据提供程序,而后该数据提供程序就会执行所请求的操作,并通过ADO库返回结果。
而在ADO.NET中,数据提供程序不涉及中间层。开发人员直接与对应的数据提供程序通信,该数据提供程序使用数据存储区的低级编程接口与数据存储区通信。使用ADO.NET的SQL Client.NET数据提供程序域SQL Server通信要快于ADO和SQL Server OLE DB提供程序,原因就在于少涉及一层。
- 扩展性更强
在SQL Server 2000引入XML特性时,ADO开发小组就面临着一项很有意义的挑战。为了给ADO添加能使开发人员从SQL Server 2000中检索XML数据的特性,ADO开发小组就必须为OLE DB API以及SQL Server OLE DB提供程序添加新的接口。
.NET数据提供程序更容易扩展。它们只需要支持相同的基础接口,并可以在适当的时候提供额外的提供程序专门功能。OLE DB.NET数据提供程序的Command对象所公开的全部方法和属性,SQL Clien.NET数据提供程序的Command对象(SqlCommand)也均予以公开,此外还添加了一个新的方法,以获得以XML方式返回的程序结果。
SQL Server 2005包含了一组新的特性,例如能够使应用程序利用通知服务,以便在服务器上查询结果发生变化时收到通知。微软并没有改变ADO.NET公开类的内部工作,而只是想SQL Client.NET数据提供程序添加了两个新类,以充分利用这些新的SQL Server特性。
2、连接对象
- ProviderFactory类
ProviderFactory类是ADO.NET2.0中新增的类,相当于一个对象工厂,使开发人员能够为.NET数据提供程序创建其他类的实例。每个ProviderFactory都提供一种Create方法,此方法创建Connections,ConnectionStringBuilders,Commands,Parameters,DataAdapters和CommandBuilders。
- Connection类
表示与数据库的连接。可通过Connection的不同属性指定数据源的类型、位置。它起到渠道的作用,其他对象如DataAdapter和Command通过它与数据库进行通信,以提交查询和获取查询结果,下面这幅图很形象的说明了Conneciton所承担的桥梁作用。
(备注:这幅图使用了冠华仔博文http://www.cnblogs.com/gishuazi/archive/2009/03/05/1403823.html中的图:_))
- ConnectionStringBuilder类
ConnectionStringBuilder类也是ADO.NET2.0中新增的,它简化了为.NET数据提供程序建立连接字符串的过程,每个ConnectionStringBuilder类都公开一些属性,这些属性对应于可在.NET数据提供程序的连接字符串中使用的选项。例如SqlConnectionStringBuilder类有DataSource属性,用于指定数据源的位置,InitialCatalog属性用于指定连接哪个数据库,IntegratedSecurity属性用于指定使用何种方式连接。
- Command类
Command对象可表示对数据库的查询,对存储过程的调用,或者返回特定表内容的直接请求。数据库支持多种不同类型的查询。有些查询通过引用一个表或多个表、视图或者是通过调用一个存储过程来获取数据行,有些查询则会对数据行进行修改,还有一些查询通过创建或修改诸如表、视图或存储过程对象来对数据库的结构进行有关操作。Command对象都能够支持。
- DataReader类
DataReader用于以最快的速度检索并检查查询所返回的行。可使用DataReader对象来检查查询结果,一次检查一行。当移向下一行时,前一行的内容就会被丢弃。DataReader不支持更新操作。由DataReader返回的数据时只读的。由于DataReader对象支持最小特性集,所以它的速度非产快。
- Transaction类
有时可能希望将对数据库所作的所有更改组织起来,将它们看做一个独立的工作单元。在数据库编程中,这样的工作单元就成为事务(Transacton)。假设某数据库包含银行的客户信息,还有支票账户表和储蓄账户表,一位用户想要将钱从储蓄账户转到支票账户中去。在所编写的代码中,希望确保储蓄账户的提款操作和支票账户的存款操作是一个独立的单元,要么同时成功完成,要么两项操作都不发生,这就可以通过事务来实现。
Connection对象有一个BeginTransaction方法,可以用来创建Transactio对象。
- Parameter类
在查询语句中,我们希望通过Where子句来限定条件,这些条件,如果希望在运行时动态指定,那么我们可以通过Parameter对象来实现。我们可以通过为查询中的所有参数创建Parameter对象,并将它们添加到Command对象的Parameter集合中。
- DataAdapter类
就像上面那幅图所示的,DataAdapter负责搬运数据到DataSet中,DataAdapter对象的Fill方法提供了一种高效机制,用于将查询结果引入DataSet或DataTable中,以便能够脱机处理数据。还可以利用DataAdapter对象向数据库提交存储在DataSet对象中的挂起更改。
DataAdapter公开了大量属性,这些属性实际上是Command对象。例如SelectCommand属性包含一个Command对象,该对象表示将用来填充DataSet对象的查询。此外,DataAdapter还有UpdateCommand、InsertCommand和DeleteCommand等属性,这么做是为了允许开发人员定义自己的更新逻辑。
二、非连接对象
1、DataTable类
ADO.NET的DataTable类运行我们通过行和列的集合来查看数据。可以通过DataAdapter对象的Fill方法将查询结果存储在DataTable中:
Dim conn As SqlConnection
Dim sqlCommand As SqlCommand
Dim sqlAdapter As SqlDataAdapter
Dim dataSet As DataSet
'完成连接数据库和填充数据
conn = New SqlConnection("Data Source=(local);Initial Catalog=JWInfo;Integrated Security=True")
sqlCommand = New SqlCommand("SELECT * FROM 学生信息;", conn)
sqlAdapter = New SqlDataAdapter(sqlCommand)
dataSet = New DataSet()
sqlAdapter.Fill(dataSet, "学生信息")
在从数据库中读出数据并将其存储在DataTable对象之后,该数据即从服务器断开连接。然后就可以脱机查看DataTable对象的内容,而不会在ADO.NET和数据库之间产生任何网络通信流量。DataTable类包含了其他非连接对象的集合,例如可以通过DataTable的Rows属性访问其内容,这一操作将返回DataRow对象的一个集合。如果希望查看DataTable表的结构,则可以使用其Columns属性来获取DataColumn的对象集合。DataTable还允许为该类中存储的数据定义一些约束,如主键。可以通过DataTable对象的Constrains属性访问这些约束。
2、DataColumn类
每个DataTable都有一个DataColum的集合,该集合时DataColum对象的容器,从其名称可以看出,一个DataColumn对象对应于表中的一列,然而,DataColumn对象并非实际包含存储在DataTable中的数据,而是存储了有关该列的结构信息,例如数据类型,是否是只读的,是否允许存储Null值,是否是唯一的等等。
DataColumn类还包含一个Expression属性,用来指定如何计算列中的数据。通常我们会通过表达式来计算查询中一个列的值,例如:
SELECT OrderID, ProductID, Quantity, UnitPrice, Quantity*UnitPrice AS ItemTotal From [Order Details]
这样做的缺点是:数据库引擎只会在查询时执行计算。如果修改了DataTable对象中的UnitPrice或者Quantity列的内容,ItemTotal列不会发生任何变化。
ADO.NET的DataColumn类定义了一个Expression属性来完美地解决这个问题。当基于一个表达式来检查DataColumn对象的值时,ADO.NET会计算该表达式,并返回一个最新的值。这样,如果更新了表达式中任一列的值,存储在计算列中的值都是准确的。下面是具体的使用方法:
DataColumn col=new DataColumn();
col.ColumnName="ItemTotal";
col.DataType=typeof(Decimal);
col.Expression="UnitPrice * Quantity";
3、Constriant类
DataTable类还提供了一种方式,用于对DataTable对象中本地存储的数据设置约束。例如,可以建立一个Constraint对象,确保某一列或多列中的值在DataTable中式唯一的。Constraint对象存在于DataTable的Constraints集合中。
4、DataRow类
要想访问存储在DataTable对象中的实际值,可以使用此对象的Rows集合,该集合中包含一组DataRow对象。要想查看存储在特定行、特定列中的数据,可以使用对应DataRow对象的Item属性来读取该行中任意列的值。DataRow的Item属性有多个重载版本,可以通过指定列名称,索引值,甚至DataColumn对象来明确要查看哪一列。由于Item是DataRow的默认属性,因此可以隐式地使用它:
DataRow row;
row=tbl.Rows[0];
Console.WriteLine(row[0]);
Console.WriteLine(row["CustomerID"]);
Console.WriteLine(row[MyTable.Columns["CustomerID"]]);
可以通过调用DataRow对象的BeginEdit方法,通过Item属性修改此行中的一些列的值,然后通过EndEdit方法来讲更改保存到改行中。通过调用DataRow的CancelEdit方法,可以取消在当前编辑会话中所作的修改。
需要注意的是,在改变了一行的内容时,DataRow对象会缓存这些更改,从而可以在以后想数据库提交他们。
5、DataSet类
从其名称可以看出,DataSet对象包含一个数据集。可以将DataSet对象视为许多DataTable对象(它们存储在DataSet对象的DataTables集合中)的容器,也就是说相当于一个内存的数据库。ADO.NET的目的是帮助开发人员建立大型的多层数据库应用程序。有时,开发人员可能希望访问一个运行在中间层服务器上的组件,以获取许多表的内容。这时不必重复调用该服务器以便每次从一个表中获取数据,而是可以将所有的数据都装入一个DataSet对象中,并在一次单独调用中将其返回。但DataSet对象的功能绝不仅仅作为多个DataTable对象的容器。
存储在DataSet对象中的数据未与数据库连接。对数据所作的任何修改都将只是缓存在每个DataRow中。要将这些更改传递给数据库时,将整个DataSet对象回传给中间层服务器可能并非一种有效方法。可以使用GetChanges方法仅从DataSet中选出被修改的行。通过这样的方式,可以在不同进程或服务器之间传递较少的数据。
DataSet还公开了一个Merge方法,该方法可以作为GetChanges方法的一个补充。用于向数据库提交更改的中间层服务器(它使用的是由Merge方法返回的较小的DataSet)将会返回一个包含着新获得数据的DataSet。可以使用DataSet类的Merge方法来将两个DataSet对象的内容合并入一个DataSet中。
我们还可以在不建立与数据库连接的情况下,就是用信息填充DataSet对象的Tables表集合。在以前的数据库编程模型中,在本地添加新行之前,通常需要查询数据库,然后将它们提交给数据库。而使用ADO.NET,在准备好提交数据行之后,才需要与数据库通信。
6、DataRelation类
数据库中的表,通常以某种方式相关联。例如,在Northwind数据库中,Orders表中的每个项都与Customers表的一个表项相关联,因此可以确定哪位客户下了哪些订单。ADO.NET的DataRelation对象借助于DataRelation类来处理来自相关DataTable对象的数据。
DataTable类公开了一个Relations属性,该属性是DataRelation对象的一个集合。可以使用DataRelation对象来表示DataSet中不同DataTable对象之间的关系。
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();
}
7、DataView类
在将一查询结果置于DataTable对象中以后,就可以使用DataView对象以不同的方式来查看数据。如果希望根据某一列对DataTable对象的内容进行排序,只需要将DataView的Sort属性设置为该列的名称即可。还可以设置DataView的Filter属性,使得只有符合特定标准的行可见。
可以使用多个DataView对象同时查看同一个DataTable。例如在一个窗体中可以拥有两个表格,其中一个用于按照字母顺序显示所有客户,另一个则按照国家进行排序。为了显示所有视图,需要将每个表格绑定到不同的DataView对象。