UML软件工程组织

ADO.NET 与您
  ADO.NET 与您

Rockford Lhotka
Magenic Technologies

2004年9月1 日

从 MSDN Code Center 下载 vbADO.exe 示例文件(英文)。

Microsoft .NET 包括一种新的数据访问技术,称为 ADO.NET。采用这种新技术的原因有很多,其中包括:对断开连接的数据的内部支持、全面的 XML 支持以及该技术与整个 .NET 框架的无缝整合。

本文着重讨论该技术包含哪些功能,以及如何充分利用这一技术来使用数据库。我们首先介绍连接到数据库的基本要素,然后讨论如何读取和写入数据库。ADO.NET 的内容十分广泛,针对每种操作提供了很多方法。在本文中,我们将使用 DataSet 对象读取和写入数据,并尝试使用 DataReaderCommand 对象手动读取和写入数据。同时还将探索动态 SQL 和存储过程的使用。读完本文后,您将可以使用 ADO.NET 来创建、读取、更新和删除数据。

大多数 Visual Basic® .NET 项目类型都自动引用 ADO.NET,包括 Windows® 应用程序、ASP.NET Web 应用程序和类库项目。此外,在这些项目类型的某个项目级别上,还会自动导入 System.Data 命名空间。您可以在项目的 Property(属性)对话框中看到全局 Import 语句列表。

图 1:显示在项目 Property(属性)对话框中的项目级导入

System.Data 命名空间对应用程序提供基本的数据支持,主要着重于数据的使用而不是数据访问本身。这意味着,在默认情况下,我们可以访问 DataSet 对象及其相关子对象,也可以访问定义如何进行数据访问的基本界面。

但是,在能够与数据库进行交互之前,我们需要确定要使用的数据提供程序。Microsoft .NET 包括两个现成的数据提供程序。SQL Server 提供程序提供对 Microsoft SQL Server™ 7.0 或更高版本的优化访问。OleDb 提供程序提供对具有 OleDb 提供程序的所有数据库的访问。

SQL Server 提供程序位于 System.Data.SqlClient 命名空间。该提供程序针对 SQL Server 访问进行了优化,与通过 OLE DB 使用 ADO 相比,它能够提供更好的性能。此外,该提供程序还具有一个智能连接缓冲池机制,因此,与它的前任 OLE DB 或 ODBC 相比,能够提供更快的缓冲数据库连接访问。

OleDb 提供程序位于 System.Data.OleDb 命名空间。使用此提供程序,我们可以通过 OLE DB 与数据库进行交互。任何包含现有 OLE DB 提供程序的数据库都可以通过 .NET 中的 OleDb 提供程序进行访问。

还有一种 Odbc 提供程序,您可以从 MSDN 下载该程序。它是 ODBC 的包装程序,允许您与任何具有 ODBC 驱动程序的数据库进行交互。

连接到数据库

数据库连接由用于数据库的提供程序进行处理。所有提供程序都会提供一个连接对象。SQL 提供程序包含 SqlConnection 对象,而 OleDb 提供程序包含 OleDbConnection 对象。这两种连接对象都使用连接字符串和其他信息(如安全凭据)来定义数据库的位置。

OleDbConnection 使用普通的 OLE DB 连接字符串,这并不奇怪,因为它只是将字符串传递给基础 OLE DB 提供程序。下面是用于 Access 数据库的 OleDbConnection 字符串示例:

Provider=Microsoft.Jet.OLEDB.4.0;Password="";User ID=Admin;Data 
Source=grocertogo.mdb

SqlConnection 使用类似的连接字符串,但不需要数据库提供程序,而只需要有关如何查找数据库的信息和安全凭据。下面是 SQL Server 的 Pubs 数据库的示例:

data source=localhost;initial catalog=pubs;user id=sa;password=

在许多情况下,甚至在手动创建连接对象时,我们也不必担心如何创建自己的连接字符串。Visual Studio® .NET IDE 包含的功能使我们无需键入任何代码行,即可轻松地添加和配置连接。

在工具箱中,您可以找到 Data(数据)选项卡,其中包括常见的数据对象,例如 DataSetOleDbConnectionSqlConnection 对象。您可以将这些对象拖放到 Windows 窗体、Web 窗体或组件设计器中。

图 2:工具箱中的 Data(数据)选项卡包含常见的数据对象

这些对象为非图形实体,因此不会直接显示在窗体上,而是显示在设计器底部的图标栏中,以便于用户访问。这些实体在很多方面都与控件类似,因此当单击这些实体时,您可以使用普通的 Properties(属性)窗口更改它们的属性和设置。

例如,我们创建一个名为 ADO 的 Windows 应用程序,并将一个 SqlConnection 对象拖放到窗体中。该对象将出现在控件图标栏中。然后,可以使用 Properties(属性)窗口将其名称更改为一个更具说明性的名称,例如 cnPubs

图 3:添加到 Windows 窗体的 SqlConnection 对象

向导是一个更加有用的工具,它可以帮助我们构造连接字符串。在 Properties(属性)窗口中单击 ConnectionString 字段,然后单击该字段中显示的下箭头。其中将列出为该控件定义的所有连接以及一个 <New Connection...>(<新建连接...>)条目。

如果单击 <New Connection...>(<新建连接...>)条目,Visual Studio .NET 将显示一个对话框以定义数据源。

单击 OleDbConnection 对象将显示标准的 OLE DB 连接对话框,您可以从中选择 OLE DB 数据提供程序,并指定该提供程序所需的全部信息。单击 SqlConnection 对象将显示一个对话框,您可以从中选择 SQL Server、定义安全设置以及选择该服务器上的数据库。

这里,我们设置一个指向本地计算机上的 Pubs 数据库的连接,在后面的示例中将使用该连接。cnPubs 的设置存放在窗体中由 Windows 窗体设计器生成的代码区域中,默认情况下,这些设置被隐藏。这与窗体中的其他控件没有什么不同。

您也可以选择手动配置连接对象。为此,需要使用类似如下的代码:

    Dim cnPubs As New SqlConnection()
    cnPubs.ConnectionString = _
     "data source=localhost;initial catalog=pubs;integrated security=SSPI"

这些基本代码与设计器为我们创建的基本代码相同。

打开和关闭连接

请注意,无论使用设计器还是手动代码,都只是定义了连接,而并未打开连接。我们对何时打开和关闭连接拥有完全的控制权,因此可以在应用程序中进行最有效的操作。

请记住,SqlClient 会缓冲数据库连接,因此,作为一般原则,最好将系统设计为打开连接、使用连接,并在完成操作后立即关闭连接。使用 OleDb 客户端时,对于用于您的数据库的特定 OLE DB 提供程序,您需要遵循某些最佳的实践经验。

使用正确的错误处理以确保数据库连接始终被关闭(即便出现错误时),这一点非常重要。使用数据库连接时,建议采用以下结构:

    Try
      cnPubs.Open()
      Try
        ' 此处进行数据访问
      Finally
        cnPubs.Close()
      End Try
    Catch
      ' 此处处理数据访问错误
    End Try

外层的 Try..Catch 块允许您处理可能发生的任何数据访问错误,包括无法打开连接,以及在读取或更新数据时可能发生的任何错误。

内层的 Try..Finally 块可以确保即使在读取或更新数据时确实发生错误的情况下,连接仍然被关闭。Finally 构造不会重置错误,因此所有错误都将由外层的 Catch 构造捕获。但是,不论是否发生错误,都将运行 Finally 块,从而确保正确关闭连接。

其实许多情况下都不需要此结构,因为我们即将使用的其他 ADO.NET 对象通常会自动打开或关闭连接。一般来讲,针对您编写的大量额外的数据访问代码,ADO.NET 很可能可以通过其他对象或方法提供更简单的解决方案。

使用数据

我们已经知道了如何定义、打开和关闭数据库连接,下面我们将通过读取和写入数据库来使用数据。

数据库交互最终是通过 Command 对象来完成的,尽管这些对象可能隐藏在其他更为抽象的对象后面。Command 对象包含有关如何读取或更新数据库中的数据的指令。这些指令可能是动态 SQL 语句,也可能是调用存储过程所需的信息。

读取数据通常有两种方法。其一是 DataSet 对象,它提供一个我们可以使用的、抽象的、断开连接的数据副本。DataSetDataAdapter 对象填充,DataAdapter 对象又与基础 CommandConnection 对象进行交互。

DataAdapter 使用一个名为 DataReader 的对象从数据库中实际获取数据。然后,这些数据将被用来填充 DataSet 对象。

图 4:由 DataAdapter 加载的 DataSet

第二种读取数据的方法是直接使用 DataReader,从而绕过整个 DataSetDataAdapter 技术。这种方法在创建 Web 页或 XML Web Services 时尤为有用,因为我们通常只是从数据库中提取数据,然后简单地将其复制到页面或 XML 输出结果中。将数据复制到 DataSet,然后再将其复制到页面或 XML 结果需要花费额外的时间。

图 5:直接调用 Command 对象获取 DataReader

尽管这种手动方法有时比较快,但与使用 DataAdapterDataSet 相比,它需要我们做更多事情。

DataSetDataAdapter 对象还提供了更新数据的机制。DataAdapter 不仅包含检索数据的 Command 对象,还包含插入、更新和删除数据的 Command 对象。您只需将已更改的 DataSet 对象传递到 DataAdapter,后者将自动调用相应的 Command 对象将更改更新到数据库中。

默认情况下,此更新进程使用优化的并发处理,也就是说,进行更新之前将检查基础数据。如果用户更改了数据,则不会应用更新,并将通知代码发生了冲突。您可以禁用这种行为,以免盲目地将更新写入数据库,而不管其他用户所做的更改。

此外,您也可以选择通过直接调用 Command 对象(其中包含更新数据库所需的指令)手动更新数据库。在这种情况下,我们需要处理任何并发问题。

使用 DataSet 读取和更新数据

DataSet 是保存在内存中供您使用的断开连接的数据副本。要从数据提供程序中填充 DataSet,我们需要使用 DataAdapter 对象,该对象将使用 Command 对象生成 DataReader,然后可以从 DataReader 中将数据读入 DataSet

创建和配置 DataSet 的方法有很多,最简单的方法是使用 Visual Studio .NET。您也可以手动配置 DataSet,但是就象处理连接对象一样,您所编写的代码与 Visual Studio .NET 自动生成的代码效果一样。

有趣的是,通过 Visual Studio .NET 创建 DataSet 其实是这样一个过程,即创建 DataAdapter 对象,然后让 DataAdapter 对象自动生成 DataSet

从工具箱的 OleDb 选项卡将 SqlDataAdapter 对象拖放到窗体中。这将启动向导。

首先显示说明面板,然后,向导的下一个面板将询问要使用的连接对象。我们只有一个连接,因此很容易选择。请注意,您也可以从此面板创建一个连接,然后使用 DataAdapter 开始整个过程,这样便可以通过此向导指导您完成整个过程。

在下一个面板中,要执行的任务就比较复杂了。请记住,DataAdapter 实际上是由四个 Command 对象组成的集合。在此面板中,您需要定义如何配置这些 Command 对象。它们可以使用动态 SQL 语句,可以调用数据库中现有的存储过程,也可以由向导自动创建新的存储过程。当然,最后一个选项要求您具有向数据库中添加存储过程的安全访问权限。

下一个面板取决于所选择的选项。如果选择使用 SQL 语句或创建新的存储过程,您可以输入 SELECT 语句或使用查询生成器构建一个存储过程。执行插入、更新和删除操作的其他三个命令将基于 SELECT 语句自动构建。

如果选择现有的存储过程,下一个面板将允许您选择四个存储过程以分别与四个命令相关联。

在本示例中,我们使用包含 SELECT 语句的 SQL 语句,如下所示:

SELECT
    au_id, 
    au_lname, 
    au_fname
FROM
    Authors

向导的最后一个面板只是确认我们指定的操作。单击 Finish(完成)后,用于配置 DataAdapter 的代码将被保存到窗体或其他设计器的隐藏代码区域。

Properties(属性)窗口中,将 DataAdapter 对象的名称更改为 daAuthors,因为它被配置为与 authors 表一起使用。

您可以右击控件图标栏中的 DataAdapter,然后选择 Configure Data Adapter(配置数据适配器)选项,从而随时更改这些选项。

您也可以右击控件并选择 Generate Dataset(生成数据集),使 DataAdapter 自动创建代表数据的 DataSet 对象。这是配置 DataSet 对象的最简单的方法,我们将采用此方法。

这将显示一个对话框,您可以从中将 DataAdapter 与现有的 DataSet 相关联,或者创建一个新的 DataSet。在本示例中,我们将创建一个名为 dsPubs 的新 DataSet

单击 OK(确定),即可创建 DataSet。一个名为 dsPubs1.xsd 的新文件将添加到项目中,一个名为 dsPubs1 的新控件将添加到控件图标栏中。

xsd 文件是一个 XML 架构文件,用于定义控件所保持的数据结构。该架构是根据 DataAdapter 对象中的 SELECT 语句或存储过程自动生成的。

单击 dsPubs1 控件,然后在 Properties(属性)窗口中将其名称更改为 dsPubs。该控件就是我们的 DataSet,它现在已经准备就绪,可以通过编程方式或数据绑定使用 DataAdapter 中的数据了。

在窗体中添加一个 DataGrid 和两个按钮,如图 6 所示。

图 6:在窗体中添加 DataGrid 和两个按钮

选择 DataGrid,然后使用 Properties(属性)窗口将其 DataSource 属性设置为 dsPubs.Authors。这会将网格绑定到 DataSet,并会使网格立即显示正确的列名称。由于项目中已添加 xsd 架构文件,因此所有数据都可用。

剩下的工作只是添加一些代码,使用 DataAdapter 来加载 DataSet。在 Refresh data(刷新数据)按钮后面添加此代码:

  Private Sub btnRefresh_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnRefresh.Click

    daAuthors.Fill(DsPubs.authors)

  End Sub

请注意,这里我们没有打开或关闭连接。因为 DataAdapter 可以为我们自动完成此操作。它可以打开连接,获取数据并关闭连接,而无需我们额外编写任何代码。

同样,我们可以在 Save Changes(保存更改)按钮后面添加代码以更新数据:

  Private Sub btnSave_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnSave.Click

    daAuthors.Update(DsPubs.authors)

  End Sub

由于 DataAdapter 包含可以执行插入、更新和删除操作的 Command 对象,因此只需调用 Update 方法便可以自动完成所有任务。DataSet 本身会跟踪每行数据是否被更改、插入或删除,因此这一技术只会将更改过的数据更新到数据库中。

由于网格被绑定到 DataSet,当我们运行该应用程序并单击 Refresh(刷新)按钮时,将显示数据,并且可以编辑这些数据。仅在单击 Save Changes(保存更改)按钮后,更改才会保存到数据库中。

这显示了 DataAdapterDataSet 对象的强大功能,同时也说明了我们对 .NET 中的数据绑定的控制能力。

此外我们还应意识到,这里所使用的 DataSet 是非常简单的。实际上,DataSet 可以同时支持多个表,并且可以知道这些表之间的关系,这有助于加强关系规则以及导航数据。DataSet 是完全断开连接的,也就是说,可以通过网络将其传递到远程客户端,而远程客户端又可以将其返回到服务器(从中可以使用 DataAdapter 将更改更新到数据库中)。

手动读取和更新数据

尽管 DataSetDataAdapter 非常简单,也非常好用,但它们并非在任何时候都是最佳选择。有时候,我们需要快速读取数据,并且不需要在内存中存储数据副本。还有些时候,我们要更新到数据库中的数据可能不是来自 DataSet,或者在将数据更新到数据库之前,不适于使用 DataSet 将数据放在内存中。

读取数据时,我们可能只需找到数据并将其发送到其他位置,例如 Web 页、XML 文档或文本文件。在这些情况下,直接使用 DataReader 对象会更有效。

为解释其工作方式,我们将数据复制到文本文件中。在窗体中添加另一个按钮,并命名为 btnCopyToFile。然后添加以下代码:

  Private Sub btnCopyToFile_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnCopyToFile.Click

    Dim cn As New SqlConnection()
    Dim cm As New SqlCommand()

    cn.ConnectionString = _
    "data source=localhost;initial catalog=pubs;integrated security=SSPI"

    Try
      cn.Open()
      Try
        cm.Connection = cn
        cm.CommandText = "SELECT au_id,au_fname,au_lname FROM authors"

        ' 此处进行数据处理

      Finally
        cn.Close()
      End Try

    Catch ex As Exception
      MsgBox(ex.ToString)
    End Try

  End Sub

这是我们前面讨论过的代码结构,它将打开和关闭 Connection 对象的操作包含在嵌套的 Try 块中,以确保无论发生什么错误,都可以关闭连接。请注意,我们配置了一个 Command 对象,以使用 SQL 语句从 authors 表中选择数据。这里,我们直接使用 Command 对象,而不是将其嵌入到 DataAdapter 中。

现在,我们可以创建一个 DataReader,它可以按照 Command 对象的指示从数据库中检索数据:

    Dim cn As New SqlConnection()
    Dim cm As New SqlCommand()
    Dim dr As SqlDataReader

    cn.ConnectionString = _
    "data source=localhost;initial catalog=pubs;integrated security=SSPI"

    Try
      cn.Open()
      Try
        cm.Connection = cn
        cm.CommandText = "SELECT au_id,au_fname,au_lname FROM authors"
        dr = cm.ExecuteReader()
        Try
          While dr.Read
            ' 此处处理每行数据
          End While

        Finally
          dr.Close()
        End Try

      Finally
        cn.Close()
      End Try

使用 DataReader 非常直截了当。Command 对象上的 ExecuteReader 方法使 Command 可以对数据库运行,并返回一个 DataReader 对象作为结果。

DataReader 对象包含一个 Read 方法,使对象可以逐行读取数据。它从 BOF(文件开始处)开始,而 BOF 不是有效行,因此在尝试读取任何数据之前,我们需要先调用一次 Read 方法。Read 方法返回一个布尔值。如果返回 False,则表明没有可供读取的数据。

DataReader 包含在 Try..Finally 块中,因此我们可以确保完成操作时可以正确将其关闭。这一点非常重要,因为只有当 DataReader 关闭后,Connection 对象才可以用于其他操作。

某些数据也要在 DataReader 关闭后才可用。例如,记录计数值 RecordsAffected 只有在 DataReader 关闭后才能确保准确性。如果使用存储过程作为 Command,则只有在 DataReader 关闭后,输出参数值才可用。

现在,剩下的工作就是添加代码,将数据从 DataReader 写入文本文件:

        Dim outfile As StreamWriter
        Try
          outfile = File.CreateText("c:\authors.txt")
          While dr.Read
            With outfile
              .Write(dr("au_id"))
              .Write(",")
              .Write(dr("au_lname"))
              .Write(",")
              .Write(dr("au_fname"))
              .WriteLine()
            End With
          End While

        Finally
          outfile.Close()
          dr.Close()
        End Try

这里使用了列名称在行中建立索引,以检索每一列。您也可以使用数字索引,从零开始累计列数。可以使用 FieldCount 方法返回行中的列数。

还有一种更好的方法,即同时使用数字索引和 DataReader 的特定类型方法来检索每个字段。上面的代码使用了后期绑定,并且不是特定于类型的,因而执行速度较慢。实际上,这里每个数据元素都作为 Object 类型返回,从而必须将其转换为相应的类型才能使用。

我们可以使用诸如 GetStringGetInteger 等方法,它们将以早期绑定和特定类型的方式返回数据。为此,可以编写如下代码:

            With outfile
              .Write(dr.GetString(0))
              .Write(",")
              .Write(dr.GetString(1))
              .Write(",")
              .Write(dr.GetString(2))
              .WriteLine()
            End With

DataReader 包括每种数据类型的方法,您可以根据需要检索数据。如果尝试使用错误的类型方法检索数据,将会收到运行时错误。例如,尝试使用 GetString 检索 Integer 将导致错误。显然,使用字段名可以提高可读性,而使用数字索引连同特定类型方法可以提高性能。

遍历所有数据之后,关闭文本文件。请注意,该文件也包含在 Finally 块中,因此,我们可以确保即使处理数据时发生错误,文件也可以关闭。

我们也可以直接调用 Command 对象手动更新数据。如果要更新的数据不是存储在 DataSet 中,则可以使用此方法。在本示例中,我们从文本文件中读回数据,并更新数据库。

在窗体中添加一个名为 btnFileToDb 的按钮,其代码如下:

  Private Sub btnFileToDb_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnFileToDb.Click

    Dim cn As New SqlConnection()
    Dim cm As New SqlCommand()
    Dim infile As StreamReader

    cn.ConnectionString = _
    "data source=localhost;initial catalog=pubs;integrated security=SSPI"

    infile = File.OpenText("c:\authors.txt")

    Try
      cn.Open()
      Try
        Dim instr As String
        Dim indata() As String

        cm.Connection = cn
        cm.CommandText = "UPDATE authors " & _
          "SET au_fname=@fname,au_lname=@lname " & _
          "WHERE au_id=@id"
        cm.Parameters.Add(New SqlParameter("@id", SqlDbType.VarChar))
        cm.Parameters.Add(New SqlParameter("@fname", SqlDbType.VarChar))
        cm.Parameters.Add(New SqlParameter("@lname", SqlDbType.VarChar))

        While infile.Peek > -1
          instr = infile.ReadLine
          Debug.WriteLine(instr)
          indata = Split(instr, ",")
          cm.Parameters("@id").Value = indata(0)
          cm.Parameters("@lname").Value = indata(1)
          cm.Parameters("@fname").Value = indata(2)
          cm.ExecuteNonQuery()
        End While

      Finally
        cn.Close()
      End Try

    Catch ex As Exception
      MsgBox(ex.ToString)

    Finally
      infile.Close()
    End Try

  End Sub

同样,我们采用相同的错误处理基本结构,以确保在完成操作之后,文件和数据库连接都被关闭。

代码中令人感兴趣的是我们配置 Command 对象的位置:

        cm.Connection = cn
        cm.CommandText = "UPDATE authors " & _
          "SET au_fname=@fname,au_lname=@lname " & _
          "WHERE au_id=@id"
        cm.Parameters.Add("@id", SqlDbType.VarChar)
        cm.Parameters.Add("@fname", SqlDbType.VarChar)
        cm.Parameters.Add("@lname", SqlDbType.VarChar)

由于要在循环中使用此对象,我们使用参数对其进行了预配置。这与用来调用接受参数的存储过程的基本机制相同。

Command 对象配置为根据参数更新数据后,我们可以遍历文本文件,分析每行文本并对每行执行 Command 对象:

        While infile.Peek > -1
          instr = infile.ReadLine
          Debug.WriteLine(instr)
          indata = Split(instr, ",")
          cm.Parameters("@id").Value = indata(0)
          cm.Parameters("@lname").Value = indata(1)
          cm.Parameters("@fname").Value = indata(2)
          cm.ExecuteNonQuery()
        End While

上述代码说明了如何执行 Command 对象进行插入、更新或删除操作,同时也说明了如何使用 StreamReader 类轻松读取和分析以逗号分隔的文本文件的内容。

调用存储过程

我们已经知道如何使用 DataAdapter 来调用 Command 对象,或者如何使用 SQL 语句直接与数据库进行交互。Command 对象还可用于调用存储过程,这通常是数据库交互的首选方法,因为与动态 SQL 语句相比,存储过程通常能够提供更好的性能。

假设 Pubs 数据库包括一个检索作者数据的存储过程:

CREATE PROCEDURE getAuthors
AS
   SELECT au_id,au_fname,au_lname 
   FROM authors
   RETURN

我们可以更改 btnCopyToFile 的代码,以使用此存储过程而不是动态 SQL 语句。代码如下所示:

        cm.Connection = cn
        cm.CommandType = CommandType.StoredProcedure
        cm.CommandText = "getAuthors"

CommandType 属性被更改为调用存储过程;CommandText 属性提供了存储过程的名称,而不是 SQL 语句本身。

可以使用存储过程来处理更新,例如:

CREATE PROCEDURE updateAuthor
   (
      @id varchar(11),
      @fname varchar(20),
      @lname varchar(40)
   )
AS
    UPDATE authors 
      SET   au_fname=@fname,
            au_lname=@lname
      WHERE au_id=@id
   RETURN

在本示例中,可以更改 btnFileToDb 的代码以使用存储过程:

        cm.Connection = cn
        cm.CommandType = CommandType.StoredProcedure
        cm.CommandText = "updateAuthor"

这一更改非常容易,因为我们已经在 SQL 语句中使用了参数,代码已经被编写为向 Command 对象提供必要的参数值。

使用存储过程的另一种可能是,我们使用一个包含从存储过程返回的值的输出参数。在这种情况下,我们需要更改参数的配置,以指定相应的 Direction 属性值:

        cm.Parameters.Add("@id", SqlDbType.Int)
        cm.Parameters("@id").Direction = ParameterDirection.Output

如果存储过程返回一个自动生成的数字 id 值,该参数配置就是适当的。

数据库项目

下面要讨论的最后一个主题是 Visual Studio .NET 中的数据库项目。相对于 Visual Studio .NET 而言,这并不是真正的 ADO.NET 功能,但在使用数据库时对此有所了解还是很有帮助的。

您可以按图 7 所示在解决方案中添加数据库项目。它是 Other Projects(其他项目)类别中的一个选项。

图 7:在解决方案中添加数据库项目

数据库项目的优点在于它能提供一种简便方法,将所有的数据库脚本(例如用于创建存储过程的脚本)直接保存在解决方案中。

通过服务器资源管理器可以直接访问和使用数据库,包括创建和编辑存储过程。但是,服务器资源管理器将所有数据直接存储在数据库中。这种方法并不总是让人满意,因为我们可能需要包装整个解决方案,并将其发送给其他开发人员,或者我们可能希望存储过程位于源控件下。

这时,我们就可以使用数据库项目。数据库项目由数据库连接和数据库脚本组成。例如,图 8 显示的 ADO 解决方案添加了一个名为 PubsDb 的数据库项目。在本示例中,我们将前面提到的 getAuthorsupdateAuthor 存储过程拖放到项目中。

图 8:解决方案中的数据库项目

将存储过程脚本拖放到项目中后,我们可以双击脚本,在显示的编辑器中处理这些脚本。所有更改都将保存回项目的脚本文件。

如果要将更改应用到数据库本身,可以在解决方案资源管理器中右击脚本项目,然后选择 Run(运行)选项。这将对数据库运行该脚本,从而应用更改。您也可以选择 Run On(运行于)选项,此时,系统将提示您确定对哪个数据库连接运行该脚本。

对于使用存储过程的任何解决方案,数据库项目都是一个很好的附加选项,因为它可以提高效率。

小结

ADO.NET 以 DataAdapterDataSet 对象的方式为我们提供了一种高级、抽象的数据访问机制。它还允许我们使用 CommandDataReader 对象与数据库进行更直接的交互。第一种方法需要的代码非常少,可以代替我们完成大量工作;而第二种方法赋予我们更多的控制能力,可以针对相应情况获得更好的性能。通过这些选项,ADO.NET 提供了代码的高可维护性和优良的性能,从而使之成为一种强大的高性能数据技术。

 

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