现在我们来创建一个用于从 Oracle 数据库中检索数据的 ODP.NET 应用程序。然后,我们将了解如何使用 ODP.NET 执行错误处理,以及如何处理其他数据检索情况。

在启动 Visual Studio 之后,第一个任务是创建一个工程。可以按如下所示选择 File | New | Project,也可以单击 File 正下方的 New Project 按钮。

图 1
图 1 在 Visual Studio 2008 Service Pack 1 中创建新工程

出现一个 New Project 对话框。在对话框左侧的 Project Types 下,选择您的编程语言。在这个例子中,我们选择“Visual Basic”。在 Visual Studio installed templates 下方的右侧,选择一个工程模板。为简单起见,我们选择“Windows Forms Application”。

图 2
图 2 使用 New Project 对话框

您希望指定有意义的工程名称(我们使用 OraWinApp)和解决方案名称(我们使用 OraWinApp)。一个解决方案包含一个或多个工程。当一个解决方案仅包含一个工程时,许多人对二者使用相同的名称。

添加引用

由于我们的工程必须与 Oracle 数据库连接,因此必须添加一个到包含所选数据提供程序的 ODP.NET DLL 的引用。在 Solution Explorer 内,选择工程名称,右键单击并选择 Add Reference。或者,您可以转至菜单栏并选择 Project,然后选择 Add Reference

图 3
图 3 添加引用

出现 Add Reference 对话框。

图 4
图 4 选择 ODP.NET 管理的数据提供程序

ODP.NET 位于 Oracle.DataAccess 组件名下。从列表中选择 Oracle.DataAccess,然后单击 OK 使工程知道 ODP.NET 数据提供程序。

Visual Basic/C# 语句

添加引用之后,标准的做法是添加 Visual Basic Imports 语句或 C# using 语句。从技术上讲,这些语句不是必需的,但通过它们可以让您无需使用冗长的完全限定名来引用数据库对象。

按照惯例,这些语句出现在代码文件的顶部或顶部附近,在命名空间或类声明之前。

Imports Oracle.DataAccess.Client ' Visual Basic ODP.NET Oracle managed provider

using Oracle.DataAccess.Client; // C# ODP.NET Oracle managed provider

添加完引用之后,Intellisense 将帮助您完成 Imports 或 using 语句的添加,如图 5 所示。

图 5
图 5 在 Visual Basic 中添加 Imports 语句

连接字符串和对象

Oracle 连接字符串和 Oracle 名称解析是不可分的。假定您在 tnsnames.ora 文件中定义了一个数据库别名 OraDb,如下:

OraDb=
  (DESCRIPTION=
    (ADDRESS_LIST=
      (ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521))
    )
    (CONNECT_DATA=
      (SERVER=DEDICATED)
      (SERVICE_NAME=ORCL)
    )
  )

OraDb 别名定义了客户端的数据库地址连接信息。要使用上面所述的在 tnsnames.ora 文件中定义的 OraDb 别名,您需要使用以下语法:

Dim oradb As String = "Data Source=OraDb;User Id=scott;Password=tiger;" ' Visual Basic

string oradb = "Data Source=OraDb;User Id=scott;Password=tiger;"; // C#

不过,您可以修改连接字符串,这样就不需使用 tnsnames.ora 文件。只需使用在 tnsnames.ora 文件中定义别名的语句替换别名即可。

' Visual Basic 
Dim oradb As String = "Data Source=(DESCRIPTION=" _
           + "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))" _
           + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));" _
           + "User Id=scott;Password=tiger;"

// C#
string oradb = "Data Source=(DESCRIPTION="
             + "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))"
             + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));"
             + "User Id=scott;Password=tiger;";

正如您在上面看到的那样,用户名和口令是以不加密的文本形式嵌入到连接字符串中的。这是创建连接字符串的最简单的方法。然而,从安全的角度而言不加密文本的方法是不可取的。而且,您需要了解编译的 .NET 应用程序代码仅比不加密文本形式的源代码文件稍微安全一点。可以非常简便地反编译 .NET DLL 和 EXE 文件,进而查看原始的不加密文本形式的内容。(加密实际上是正确的解决方案,但这个主题与我们这里的讨论相差太远。)

接下来,您必须从连接类中完成一个连接对象的实例化。连接字符串必须与连接对象关联。

Dim conn As New OracleConnection(oradb) ' Visual Basic

OracleConnection conn = new OracleConnection(oradb); // C#

注意,通过将连接字符串传递给连接对象的构造函数(该构造函数进行了重载),连接字符串与连接对象建立了关联。构造函数的其他重载允许使用以下这些替代的语法:

Dim conn As New OracleConnection() ' Visual Basic
conn.ConnectionString = oradb

OracleConnection conn = new OracleConnection(); // C#
conn.ConnectionString = oradb;

在连接字符串与连接对象建立关联之后,使用 Open 方法来创建实际的连接。

conn.Open() ' Visual Basic

conn.Open(); // C#

我们将在稍后介绍错误处理。

Command 对象

Command 对象用于指定执行的 SQL 命令文本 — SQL 字符串或存储过程。与 Connection 对象类似,它必须从其类中完成实例化,并拥有一个重载的构造函数。在本示例中,ODP.NET 将在 departments 表 (DEPT) 中执行 SQL 查询,并返回部门编号 (DEPTNO) 为 10 的部门名称 (DNAME)。

Dim sql As String = "select dname from dept where deptno = 10" ' Visual Basic
Dim cmd As New OracleCommand(sql, conn)
cmd.CommandType = CommandType.Text

string sql = "select dname from dept where deptno = 10"; // C#
OracleCommand cmd = new OracleCommand(sql, conn);
cmd.CommandType = CommandType.Text;

使用不同的重载时,语法的结构稍有不同。Command 对象有用于执行命令文本的方法,我们将在下一部分中讲述。不同的方法适用于不同类型的 SQL 命令。

检索标量值

可以通过实例化 OracleDataReader 对象并使用 ExecuteReader 方法从数据库中检索数据,这将返回一个 OracleDataReader 对象。通过将列名或以零为基数的列序号传递给 OracleDataReader 可以访问返回的数据。

Dim dr As OracleDataReader = cmd.ExecuteReader() ' Visual Basic
dr.Read()

Label1.Text = dr.Item("dname") ' retrieve by column name
Label1.Text = dr.Item(0) ' retrieve the first column in the select list
Label1.Text = dr.GetString(0) ' return a .NET data type
Label1.Text = dr.GetOracleString(0) ' return an Oracle data type

有适当类型的存取程序用于返回 .NET 本地数据类型,其他存取程序用于返回本地 Oracle 数据类型,所有这些存取程序都受 C#、Visual Basic 或任何其他 .NET 语言的支持。以零为基数的序号被传递给存取程序,以指定要返回的列。

OracleDataReader dr = cmd.ExecuteReader(); // C#
dr.Read();

label1.Text = dr["dname"].ToString(); // C# retrieve by column name
label1.Text = dr.GetString(0).ToString();  // return a .NET data type
label1.Text = dr.GetOracleString(0).ToString();  // return an Oracle data type

在这个简化的例子中,DNAME 的返回值是一个字符串,它用来设置标签控件的文本属性值(也是一个字符串)。但如果检索的是 DEPTNO,而不是字符串,将出现数据类型不匹配的情况。当源数据类型与目标数据类型不匹配时,.NET 运行时将尝试隐式地转换数据类型。有时数据类型不兼容,则隐式转换将失败,并跳出一个异常警报。但即使可以进行隐式转换,使用显式数据类型转换仍比用隐式数据类型转换好。

到整型的显式转换显示如下:

Label1.Text = CStr(dr.Item("deptno")) ' Visual Basic integer to string cast

C# is not as forgiving as Visual Basic on implicit conversions. You'll find yourself doing explicit conversions: 

label1.Text = dr.GetInt16("deptno").ToString(); // C#

您可以显式地转换标量值以及数组。

关闭并清除

可以调用连接对象的 CloseDispose 方法来关闭到数据库的连接。Dispose 方法调用 Close 方法。

conn.Close()   ' Visual Basic
conn.Dispose() ' Visual Basic

conn.Close();   // C#
conn.Dispose(); // C#

如果您使用 VB 的 Using 关键字或 C# 的 using 关键字,则不必显式调用 CloseDispose

using (OracleConnection conn = new OracleConnection(oradb)) // C#
{
    conn.Open();

    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = 10";
    cmd.CommandType = CommandType.Text;
	
    OracleDataReader dr = cmd.ExecuteReader();
    dr.Read();

    label1.Text = dr.GetString(0);
}

此外,OracleCommand 包含一个 Dispose 方法;OracleDataReader 包含一个 Close 方法和一个 Dispose 方法。关闭并清除 .NET 对象将释放系统资源,从而确保更高效的应用程序性能,这一点在高负载情况下尤为重要。您可以试验在上机操作 1(从数据库中检索数据)和上机操作 2(增加交互性)中学到的一些概念。

错误处理

当错误发生时,.NET 应用程序应该能够适当地处理错误,并通过提供有意义的消息来通知用户。Try-Catch-Finally 结构的错误处理是 .NET 语言的一部分;下面是使用 Try-Catch-Finally 语法的一个相对最小的示例:

' Visual Basic
Try
    conn.Open()

    Dim cmd As New OracleCommand
    cmd.Connection = conn
    cmd.CommandText = "select dname from dept where deptno = " + TextBox1.Text
    cmd.CommandType = CommandType.Text

    If dr.Read() Then
        Label1.Text = dr.Item("dname") ' or use dr.Item(0)
    End If
Catch ex As Exception ' catches any error
    MessageBox.Show(ex.Message.ToString())
Finally
    ' In a real application, put cleanup code here.
End Try

// C#
try
{
    conn.Open();

    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = " + textBox1.Text;
    cmd.CommandType = CommandType.Text;

    if (dr.Read()) // C#
    {
        label1.Text = dr["dname"].ToString();
                   // or use dr.GetOracleString(0).ToString()
    }
}
catch (Exception ex) // catches any error
{
    MessageBox.Show(ex.Message.ToString());
}
finally
{
    // In a real application, put cleanup code here.
}

虽然这种方法将适当地捕获尝试从数据库中获取数据时发生的任何错误,但这种方法对用户却不友好。例如,看看下面这条在数据库不可用时显示的消息:

图 6
图 6 捕获 ORA-12545 错误并显示给用户

Oracle DBA 或开发人员很清楚 ORA-12545 的意义,但最终用户不清楚。一种更好的解决方案是添加一条额外的 Catch 语句来捕获最常见的数据库错误并显示对用户友好的消息。

Catch ex As OracleException ' catches only Oracle errors
    Select Case ex.Number
        Case 1
            MessageBox.Show("Error attempting to insert duplicate data.")
        Case 12545
            MessageBox.Show("The database is unavailable.")
        Case Else
            MessageBox.Show("Database error: " + ex.Message.ToString())
    End Select
Catch ex As Exception ' catches any error
    MessageBox.Show(ex.Message.ToString())

catch (OracleException ex) // catches only Oracle errors
{
    switch (ex.Number)
    {
        case 1:
            MessageBox.Show("Error attempting to insert duplicate data.");
            break;
        case 12545:
            MessageBox.Show("The database is unavailable.");
            break;
        default:
            MessageBox.Show("Database error: " + ex.Message.ToString());
            break;
    }
}
catch (Exception ex) // catches any error not previously caught
{
    MessageBox.Show(ex.Message.ToString());
}

注意上述代码示例中的两条 Catch 语句。如果没有捕获到任何 Oracle 错误,那么将跳过第一条语句分支,让第二条语句来捕获任何其他非 Oracle 错误。在代码中,应该根据从特殊到一般的顺序对 Catch 语句排序。在执行完用户友好的异常处理代码之后,ORA-12545 错误消息显示如下:

图 7
图 7 ORA-12545 错误的用户友好的错误消息

无论是否发生错误,Finally 代码块总会执行。清除代码即包含在此代码块中。如果未使用 Usingusing,应清除 Finally 代码块中的连接和其他对象。

利用 DataReader 检索多个值

到目前为止,我们的示例仅说明了如何检索单个值。OracleDataReader 可以检索多列和多行的值。首先执行多列、单行查询:

select deptno, dname, loc from dept where deptno = 10

要获取列的值,可以使用以零为基数的序号或列名。序号与查询中的顺序相关。因此,可以在 Visual Basic 中通过使用 dr.Item(2) 或 dr.Item("loc") 来检索 LOC 列的值。

下面是将来自上一查询的 DNAME 和 LOC 列串连起来的代码段:

Label1.Text = "The " + dr.Item("dname") + " department is in " + dr.Item("loc") ' VB

label1.Text = "The " + dr["dname"].ToString() + " department is in " + 
              dr["loc"].ToString();  // C#

现在我们进行返回多行的查询:

select deptno, dname, loc from dept

要处理从 OracleDataReader 中返回的多个行,需要某种类型的循环结构。此外,需要一个可以显示多行的控件。OracleDataReader 是一个仅正向的只读游标,因此不能将其与可更新或完全可滚动的控件(如 Windows Forms DataGrid 控件)捆绑在一起。OracleDataReader 与 ListBox 控件兼容,如以下代码段所示:

While dr.Read() ' Visual Basic
   ListBox1.Items.Add("The " + dr.Item("dname") + " department is in " + dr.Item("loc"))
End While

while (dr.Read()) // C#
{
    listBox1.Items.Add("The " + dr["dname"].ToString() + " department is in " +
                       dr["loc"].ToString());
}

上机操作 3(利用 OracleDataReader 检索多列和多行)重点介绍了这些概念中的一部分。

在 x64 上构建和运行

在 x64 操作系统上运行 Visual Studio 2008 时,Active solution platform 默认为 Any CPU。在构建工程之前请将其更改为 x86。

图 8
图 8 在 64 位平台上构建时将 Any CPU 更改为 x86

结论

本文向您介绍了使用 .NET 编程语言访问 Oracle 数据库的过程。您现在应该能够连接数据库并检索多列和多行。

上机操作 1:从数据库中检索数据

在开始之前,您必须已经创建一个工程并添加了引用,如本文前面所述。

  1. 接着向 Windows 表单中添加一个按钮控件和一个标签控件。务必在这些控件的上方留出空间,以便在上机操作 2 中添加其他控件。 图 9
    图 9 包含按钮和标签控件的上机操作 1 表单
  2. 添加代码,它们用于从 Oracle 数据库中检索数据并在表单上显示结果。将代码放在按钮的单击事件处理程序中。开始这项任务的最容易的方式是双击该按钮,因为它将为事件处理程序创建一个 stub。

    图 10
    图 10 单击事件处理程序 stub

  3. 在 Public Class 声明之前添加 Visual Basic Imports 语句,或在命名空间声明之前添加 C# using 语句。
    Imports Oracle.DataAccess.Client ' Visual Basic, ODP.NET Oracle managed provider
    
    using Oracle.DataAccess.Client; // C#, ODP.NET Oracle managed provider
    
  4. 在 Private Sub 和 End Sub 语句之间添加 Visual Basic 版本的单击事件处理程序代码(请务必用您服务器的主机名替代 ORASRVR):
    Dim oradb As String = "Data Source=(DESCRIPTION=(ADDRESS_LIST=" _
                        + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))" _
                        + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));" _
                        + "User Id=scott;Password=tiger;"
    
    Dim conn As New OracleConnection(oradb) ' Visual Basic
    conn.Open()
    
    Dim cmd As New OracleCommand
    cmd.Connection = conn
    cmd.CommandText = "select dname from dept where deptno = 10"
    cmd.CommandType = CommandType.Text
    
    Dim dr As OracleDataReader = cmd.ExecuteReader()
    dr.Read()  ' replace this statement in next lab
    Label1.Text = dr.Item("dname") ' or dr.Item(0), remove in next lab
    
    dr.Dispose()
    cmd.Dispose()
    conn.Dispose()
    
    将以下 C# 代码添加到按钮单击事件处理程序的 { 和 } 花括号之间(请务必用您服务器的主机名替代 ORASRVR):
    string oradb = "Data Source=(DESCRIPTION=(ADDRESS_LIST="
    		 + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))"
    		 + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));"
    		 + "User Id=scott;Password=tiger;";
    
    OracleConnection conn = new OracleConnection(oradb); // C#
    conn.Open();
    
    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = 10";
    cmd.CommandType = CommandType.Text;
    
    OracleDataReader dr = cmd.ExecuteReader();
    dr.Read();  // replace this statement in next lab
    label1.Text = dr["dname"].ToString();  // remove in next lab
    
    dr.Dispose();
    cmd.Dispose();
    conn.Dispose();
  5. 运行应用程序。单击按钮。您将看到以下内容:

    图 11
    图 11 成功检索数据。


上机操作 2:增加交互性

现在在代码中实现了数据库访问的基本功能,下一步是为应用程序增加交互性。与运行硬编码的查询不同,可以添加一个文本框来接受用户输入的部门编号 (DEPTNO)。

  1. 向表单中添加一个文本框控件和另一个标签控件(如下所示):将 Label2 控件的文本属性设置为 Enter Deptno:并确保没有设置 TextBox1Text 属性。

    图 12
    图 12 包含按钮和标签控件的上机操作 2 表单

  2. 修改定义选择字符串的代码:
    cmd.CommandText = "select dname from dept where deptno = " + TextBox1.Text 'VB
    
    cmd.CommandText = "select dname from dept where deptno = " + textBox1.Text; // C#>
  3. 运行应用程序。为 DEPTNO 输入 10 测试应用程序。输入一个无效的 DEPTNO(如 50)重新测试应用程序。应用程序将退出。

    图 13
    图 13 未处理的异常

  4. 修改代码以防止在输入无效的 DEPTNO 时出现错误。让我们回顾一下,ExecuteReader 方法实际返回一个对象。将包含 dr.Read 的行替换为以下所有语句。
    If dr.Read() Then ' Visual Basic
        Label1.Text = dr.Item("dname").ToString()
    Else
        Label1.Text = "deptno not found"
    End If
    
    if (dr.Read()) // C#
    {
        label1.Text = dr["dname"].ToString();;
    }
    else
    {
        label1.Text = "deptno not found";
    }
    
    输入不存在的 DEPTNO 数字测试应用程序。现在应用程序不再退出。输入字母 A 代替数字,然后单击按钮。应用程序退出。很明显,我们的应用程序需要更好的方法来处理错误。

    可能有人会指出,应用程序应当不充许用户导致错误的无效输入,但根本上应用程序必须添加强健的错误处理功能。不是所有的错误都是可预防的,因此必须具备错误处理功能。


上机操作 3:使用 OracleDataReader 检索多列和多行

现在检索了单个值,下一步是使用 OracleDataReader 检索多列和多行。在表单中添加一个 ListBox 控件来显示结果。

  1. 在表单中添加一个 ListBox 控件。重新调整控件的大小,填满表单的大部分宽度(如下所示)。

    图 14
    图 14 添加了 ListBox 的表单

  2. 从查询中删除 where 子句,并添加以下列:
    cmd.CommandText = "select deptno, dname, loc from dept" ' Visual Basic
    
    cmd.CommandText = "select deptno, dname, loc from dept"; // C#
  3. 将在 while 循环中读取查询结果并用其填充 ListBox 控件。按如下所示修改 Visual Basic 代码:
    Dim oradb As String = "Data Source=(DESCRIPTION=(ADDRESS_LIST=" _
                + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))" _
                + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));" _
                + "User Id=scott;Password=tiger;"
    
    Dim conn As New OracleConnection(oradb) ' Visual Basic
    conn.Open()
    
    Dim cmd As New OracleCommand
    cmd.Connection = conn
    cmd.CommandText = "select deptno, dname, loc from dept"
    cmd.CommandType = CommandType.Text
    
    Dim dr As OracleDataReader = cmd.ExecuteReader()
    While dr.Read()
        ListBox1.Items.Add("The " + dr.Item("dname") + _
                           " department is in " + dr.Item("loc"))
    End While
    
    dr.Dispose()
    cmd.Dispose()
    conn.Dispose()
    Modify your C# code to look like this: 
    string oradb = "Data Source=(DESCRIPTION=(ADDRESS_LIST="
                    + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521)))"
                    + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));"
                    + "User Id=scott;Password=tiger;";
    
    OracleConnection conn = new OracleConnection(oradb); // C#
    conn.Open();
    
    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select deptno, dname, loc from dept";
    cmd.CommandType = CommandType.Text;
    
    OracleDataReader dr = cmd.ExecuteReader();
    while (dr.Read())
    {
       listBox1.Items.Add("The " + dr["dname"].ToString() +
                          " department is in " + dr["loc"].ToString());
    }
    
    dr.Dispose();
    cmd.Dispose();
    conn.Dispose();
  4. 运行应用程序。ListBox 中应该填充了 DEPT 表中的所有部门名称和位置。供下载的代码中已经具备了错误处理功能。

本文转载:CSDN博客