UML软件工程组织

用ADO.Net实现通用数据库编程
作者:王凯明 本文选自:赛迪网 2003年03月07日
ADO.Net为我们提供了一种全新的数据库访问机制,它使得数据库编程变得相当容易。然而,在运用ADO.Net进行数据库编程时,我们往往会因为不注意某些细节问题而使得应用程序的可扩展性很差,也即某个数据库应用程序只能应用于某个特定的数据库,而不能和更多的其他类型的数据库进行交互。本文我就向大家介绍如何运用ADO.Net中的接口(Interface)技术来实现通用的数据库编程。

ADO.Net体系结构


在具体介绍如何实现通用数据库编程前,我先向大家简要的介绍一下ADO.Net的体系结构。ADO.Net是由一系列的数据库相关类和接口组成的,它的基石是XML技术,所以通过ADO.Net我们不仅能访问关系型数据库中的数据,而且还能访问层次化的XML数据。

ADO.Net为我们提供了两种数据访问的模式:一种为连接模式(Connected),另一种为非连接模式(Disconnected)。运用过ADO技术的编程人员对前一种模式应该是非常熟悉的,而后一种模式则是ADO.Net中才具有的。相比于传统的数据库访问模式,非连接的模式为我们提供了更大的可升级性和灵活性。在该模式下,一旦应用程序从数据源中获得所需的数据,它就断开与原数据源的连接,并将获得的数据以XML的形式存放在主存中。在应用程序处理完数据后,它再取得与原数据源的连接并完成数据的更新工作。

ADO.Net中的DataSet类是非连接模式的核心,数据集对象(DataSet)以XML的形式存放数据。我们既可以从一个数据库中获取一个数据集对象,也可以从一个XML数据流中获取一个数据集对象。而从用户的角度来看,数据源在哪里并不重要,也是无需关心的。这样一个统一的编程模型就可被运用于任何使用了数据集对象的应用程序。

在ADO.Net体系结构中还有一个非常重要的部分就是数据提供者对象(Data Provider),它是访问数据库的必备条件。通过它,我们可以产生相应的数据集对象;同时它还提供了连接模式下的数据库访问支持。

下面的图就显示了ADO.Net总体的体系结构。



图1


数据提供者对象


本文介绍的是通用数据库编程,所以要从数据提供者对象入手,使得一个应用程序具有访问多个不同类型数据库的能力。ADO.Net中的数据提供者对象包括了数据库连接接口(IDbConnection)、数据库命令接口(IDbCommand)、数据读取器接口(IDataReader)和数据适配器接口(IDbDataAdapter)等不同种类的接口。通过这些接口,应用程序就可以访问数据库、执行相关的命令操作并获取结果,获取的结果可以是以XML数据的形式存放在数据集对象中,也可以是直接被应用程序所使用。ADO.Net中的数据提供者对象为我们提供了以上一些接口,通过这些接口每个应用程序都可以有不同的实现方法以访问特定类型的数据库。

ADO.Net中的数据提供者对象很像OLE DB中的数据提供者对象,不过它们还是有区别的,前者实现了.Net的接口,而后者则实现了COM的接口。目前,ADO.Net中包括了两种类型的数据提供者对象:一种为OleDb数据提供者对象,它可以通过COM层和OLE DB进行交互;另一种为SqlServer数据提供者对象,它是专门应用于MS SQL Server的,所以性能得到了优化。不过随着时间的推移,会有更多的数据库厂商提供专门的数据提供者对象以供大家使用的。和OleDb数据提供者对象相关的类都是以前缀OleDb命名的,而和SqlServer数据提供者对象相关的类则是以前缀Sql命名的。在实际的应用程序中,我们可以根据需要选择相应类型的数据提供者对象。不过为了使应用程序具有通用性,我们应尽量通过使用数据提供者对象的接口而并非某个特定类型的数据提供者对象来实现。

下面的表列举了两种类型的数据提供者对象中的各个并行类和相应的接口名称。

表1


接口名称 OleDb SQL Server
IDbConnection OleDbConnection SqlConnection
IDbCommand OleDbCommand SqlCommand
IDataReader OleDbDataReader SqlDataReader
IDbDataAdatpter OleDbDataAdapter SqlDataAdapter
IDbTransaction OleDbTransaction SqlTransaction
IDataParameter OleDbDataParameter SqlDataParameter



ADO.Net命名空间


.Net框架中和ADO.Net相关的命名空间包括了以下几个:System.Data、System.Data.OleDb、System.Data.SqlClient、System.Data.SqlTypes以及System.Data.Common。其中System.Data中包含了组成ADO.Net体系结构的一些基本类;System.Data.OleDb中包含了运用OleDb类型的数据提供者对象的类;System.Data.SqlClient中则包含了运用SqlServer类型的数据提供者对象的类;System.Data.SqlTypes中包含了代表SQL Server中的数据类型的类;System.Data.Common中则包含了被所有的数据提供者对象所共享的类。

示例数据库


通过以上一些对于ADO.Net的基本介绍,我想大家对ADO.Net至少有了个大概的了解。那么如何具体运用ADO.Net中的数据提供者对象的接口技术来实现通用数据库编程呢?下面我就通过一个实例向大家介绍具体的实现方法,该实例中运用了两种类型的数据库,一种为Access版的,另一种为SQL Server版的。相应地,前一种数据库需要通过OleDb类型的数据提供者对象来访问,而后一种的则需要通过SqlServer类型的数据提供者对象来访问。我们的实例程序就是要通过运用ADO.Net数据提供者对象的接口技术来实现访问两种类型数据库的功能。同时,我还会分别介绍在连接模式和非连接模式这两种不同的数据库访问模式下的不同的实现方法。

实例中用到的示例数据库为一个小型银行的帐号信息数据库,其中包括了三个表,分别如下:

1.Account表:存储了银行的所有的帐号信息,包括了四个字段:AccountID、Owner、AccountType以及Balance,其中AccountID为主键。

2.Transaction表:存储了每个帐号的交易信息,包括了三个字段:XactType、AccountID和Amount,并且Account表和Transaction表构成了父子关系。

3.IdGen表:该表仅包括了一个字段:NextAccountID,而且只存储了一行数据,其功能是用于产生一个新的帐号。

我们的示例数据库包括了Access和SQL Server两个不同类型的版本,你可以在源代码文件中找到一个SimpleBank.mdb文件和一个CreateSimpleBank.sql文件,前者是Access版的数据库文件,而后者是产生SQL Server版数据库的脚本文件。

连接模式下的实现方法


尽管ADO.Net提倡使用非连接的数据库访问模式,但它还是提供了对连接模式访问的支持。在本实例程序中,我就先向大家介绍在连接模式下的通用数据库编程的实现方法。

1.运用数据库连接接口

数据库应用程序的必备步骤就是连接到相应的数据库,我们的程序也不例外。对于不同类型的数据库,我们可以分别运用OleDbConnection类的对象或是SqlConnection类的对象来实现数据库的连接。这些类中包含了ConnectionString、ConnectionTimeout等属性,还提供了Open、Close等方法。通过设置相应的属性和运用某些方法我们就可以实现数据库的连接功能。不过我们在这里要运用的是IDbConnection接口的实现方法。在创建了某个类型的数据库连接对象后,我们应获取一个类型为IDbConnection的接口引用,在以后的编程过程中,我们就应该使用该接口引用而非原先创建的那个特定类型的数据库连接对象。

下面我先给出SQL Server版的实例程序,文章的后面还会给出Access版的实例程序的源代码。在连接到数据库一块中,首先创建一个SqlConnection对象,然后将该对象传递给一个类型为IDbConnection的接口引用,这样该接口引用就能引用原对象了。下面的代码显示了程序一开始连接到SQL Server数据库,然后进入一个命令循环的过程。其中代码中为粗体的部分是我们需要注意的,我们可以通过比较Access版实例程序中的相应代码来理解通过运用接口技术实现通用数据库编程的具体方法。


Module ConnectedSql
  Private connStr As String = _
   "server=localhost;uid=sa;pwd=;database=SimpleBank"
  Private conn As IDbConnection
  Dim sqlConn As New SqlConnection()
  Sub Main()
   OpenSql()
   CommandLoop()
  End Sub
  Private Sub OpenSql()
   conn = sqlConn
   conn.ConnectionString = connStr
   Console.WriteLine( _
     "Using SQL Server to access SimpleBank")
   Console.WriteLine( _
     "Database state: " & conn.State.ToString())
   conn.Open()
   Console.WriteLine(_ 
     "Database state: " & conn.State.ToString())
  End Sub


实例程序运行时,控制台将显示如下信息。


Using SQL Server to access SimpleBank
Database state: Closed
Database state: Open
Enter command, quit to exit
>

  

运用数据库命令接口

在我们创建了数据库连接对象的接口引用并完成与数据库的连接之后,我们就可以创建一个数据库命令对象来实现对数据库的实际操作。同样,数据库命令对象包括了OleDbCommand类型的对象和SqlCommand类型的对象。不过在本实例程序中我们还是要将创建的对象传递给一个类型为IDbCommand的接口引用,并在后面的程序中运用之。

接着上面的SQL Server版的实例程序,我们创建了一个SqlCommand类型的对象。


Private Function CreateCommand(ByVal query As String) _
As IDbCommand
  Return New SqlCommand(query, sqlConn)
End Function


这样我们就可以将该对象传递给它的接口引用,并在以后的程序中一直使用该接口引用了。下面的函数实现了接口引用的ExcuteNonQuery操作,其返回的结果是该操作实际被影响到的记录的数目。同时它还提供了ExcuteScalar、ExcuteReader和ExcuteXmlReader等不同的操作方法。


Private Sub RemoveAccount(ByVal id As Integer)
  Dim query As String = _
   "delete from Account where AccountId = " & id
  Dim command As IDbCommand = CreateCommand(query)
  Dim numrow As Integer = command.ExecuteNonQuery()
  Console.WriteLine("{0} rows updated", numrow)
End Sub


运用数据读取器接口

如上面提到的那样,我们在创建一个数据库命令对象后可以通过执行ExcuteReader操作来获取一个类型为IDataReader的接口引用。通过该数据读取器,我们就可以获得一个只读的而且只能向前读的数据流。数据读取器在处理大量记录的时候是非常有效的,因为任何时候它在主存中仅存储了一条当前记录,所以处理的效率是非常高的。在运用完一个数据读取器之后,我们必须显式的将其关闭,否则就得不到任何由它返回的参数或结果。数据读取器拥有一个Item属性,通过该属性我们能访问当前的记录,它还具有一个Read方法,该方法的功能就是向前读取数据,读取成功返回true,否则为false。

下面的函数运用了一个数据读取器的接口引用,并在控制台中显示Account表中的各条记录信息。


Private Sub ShowList()
  Dim query As String = "select * from Account"
  Dim command As IDbCommand = CreateCommand(query)
  Dim reader As IDataReader = command.ExecuteReader()
  While reader.Read()
   Console.WriteLine("{0} {1,-10} {2:C}", _
     reader("AccountId"), reader("Owner"), _
     reader("Balance"))
  End While
  reader.Close()
End Sub


Access版的实例程序

上面我给出了SQL Server版的实例程序的部分代码,下面则是Access版的部分代码(除去了命令循环的部分,该部分内容请参考源代码文件中的源码,其中SQL Server版的文件夹为ConnectedSql,Access版的文件夹为ConnectedJet)。通过比较代码中为粗体的那些部分,我相信你会很容易找到运用接口技术实现通用数据库编程的方法的。


' ConnectedJet.vb
Imports System
Imports System.Data
Imports System.Data.OleDb
Module ConnectedJet
  Private connStr As String = _
   "Provider=Microsoft.Jet.OLEDB.4.0;" _
   & "Data Source=C:\OI\Databases\SimpleBank.mdb"
  Private conn As IDbConnection
  Dim jetConn As New OleDbConnection()
  Sub Main()
   OpenJet()
   CommandLoop()
  End Sub
  Private Sub OpenJet()
   conn = jetConn
   conn.ConnectionString = connStr
   Console.WriteLine("Using Access DB SimpleBank.mdb")
   Console.WriteLine("Database state: " & _
     conn.State.ToString())
   conn.Open()
   Console.WriteLine("Database state: " & _
     conn.State.ToString())
  End Sub
  Private Sub CommandLoop()
...
  Private Sub ShowList()
   Dim query As String = "select * from Account"
   Dim command As IDbCommand = CreateCommand(query)
   Dim reader As IDataReader = command.ExecuteReader()
   While reader.Read()
     Console.WriteLine("{0} {1,-10} {2:C}", _
      reader("AccountId"), reader("Owner"), _
      reader("Balance"))
   End While
   reader.Close()
  End Sub
  Private Sub AddAccount(ByVal bal As Decimal, _
  ByVal owner As String, ByVal id As Integer)
   Dim query As String = "insert into Account values(" _
    & id & ", '" & owner & "', ' ', " & bal & ")"
   Dim command As IDbCommand = CreateCommand(query)
   Dim numrow As Integer = command.ExecuteNonQuery()
   Console.WriteLine("{0} rows updated", numrow)
  End Sub
  Private Sub RemoveAccount(ByVal id As Integer)
   Dim query As String = _
     "delete from Account where AccountId = " & id
   Dim command As IDbCommand = CreateCommand(query)
   Dim numrow As Integer = command.ExecuteNonQuery()
   Console.WriteLine("{0} rows updated", numrow)
  End Sub
  Private Sub ChangeAccount(ByVal id As Integer, _
  ByVal owner As String)
   Dim query As String = _
     "update Account set Owner = '" _
     & owner & "' where AccountId = " & id
   Dim command As IDbCommand = CreateCommand(query)
   Dim numrow As Integer = command.ExecuteNonQuery()
   Console.WriteLine("{0} rows updated", numrow)
  End Sub
  Private Sub ClearAccounts()
   Dim query As String = "delete from Account"
   Dim command As IDbCommand = CreateCommand(query)
   Dim numrow As Integer = command.ExecuteNonQuery()
   Console.WriteLine("{0} rows updated", numrow)
  End Sub
  Private Function CreateCommand(ByVal query As String) _
  As IDbCommand
   Return New OleDbCommand(query, jetConn)
  End Function
End Module


非连接模式下的实现方法


1.运用数据集对象

一个数据集对象能以XML的形式将数据存放在主存中,对于任何类型的数据源,它都提供了一个统一的编程模型,这就大大减轻了编程人员的工作。一个数据集对象中包含了一系列的表以及表之间的关系。而每个表中又包含了其主键、外键、字段和约束等信息,这些综合起来就决定了表的模式,表中还包含了一系列存储表数据的记录。

你可以运用以下一些方法产生一个数据集对象:

a. 运用一个数据适配器对象从一个数据库中读取模式和数据并产生数据集对象。

b. 动态创建模式并生成数据集对象。

c. 从XML数据源中获取数据集对象。

用上面的第二种和第三种方法获取的数据集对象本身就是数据库无关的,而在运用第一种方法时,我们可以通过使用类型为IDbDataAdapter的接口引用来达到通用数据库编程的目的。

2.运用数据适配器接口

数据适配器在数据集对象和其数据源之间起了一个桥梁的作用。ADO.Net中同样有两种类型的数据适配器对象,一种为OleDb类型的,另一种为SqlServer类型的。前者的相应类为OleDbDataAdapter,而后者的相应类自然为SqlDataAdapter了。一个数据适配器包括了SelectCommand、InsertCommand、UpdateCommand以及DeleteCommand等属性,通过从这些属性的名称我们不难知道其具体的作用。同时,一个数据适配器还包括了Fill和Update等方法。其中Fill方法能将模式和数据填充到数据集对象中,而Update方法则完成了数据更新的功能。

下面的函数就显示了如何根据一个类型为IDbDataAdapter的接口引用产生一个数据集对象的方法。


Private Sub FillDataSet()
	OpenDb()
	Dim query As String = "select * from Account"
	adapter = CreateAdapter(query)
	adapter.Fill(ds)
	CloseDb()
End Sub
Private Function CreateAdapter(ByVal query As String) As IDbDataAdapter
	Dim adapter As New SqlDataAdapter(query, connStr)
...
	Return adapter
End Function


同样,我们的实例程序包括了两个版本,你可以在源代码文件中找到,其中SQL Server版的文件夹为BankDbSql,Access版的文件夹为BankDbJet。

总结


以上我向大家介绍了在ADO.Net中如何运用接口技术实现通用数据库编程的方法,这项技术是非常有用的,而平常我们却很容易将其忽视掉,这样的结果就是应用程序的可升级性、灵活性和通用性大打折扣。所以希望大家能好好掌握这项技术,在实际运用中编写出通用性很强的应用程序,以满足更多的实际需要。

源代码文件为Source.rar[下载源码]

 

 



版权所有:UML软件工程组织