隨著 Microsoft 的 .NET 框架的逐漸流行,許多開發人員迫切想了解關於將 .NET 應用程序與 Oracle 集成的最好的方式的信息 — 不僅在基本的連通性方面,還包括與使用 Visual Studio.NET (VS.Net) 進行有效的應用程序開發的關系。
在本文中,我將說明構建使用 Oracle 數據庫的 .Net 應用程序所涉及到的基本但不可或缺的過程,包括:
.Net Data Provider
除了基本的 Oracle 客戶端連通性軟件,.NET 應用程序還需要使用稱為 managed data provider(其中 "managed" 指的是代碼由 .NET 框架管理)的工具。 數據供應程序是 .NET 應用程序代碼和 Oracle 客戶端連通性軟件之間的層。 在幾乎所有情況下,最優的性能都是通過使用為特定數據庫平台優化了的供應程序而不是一般的 .Net OLE DB 數據供應程序實現的。
Oracle、Microsoft 和第三方供應商都提供了針對 Oracle 產品進行了優化的數據供應程序。 Oracle 和 Microsoft 免費提供其 Oracle 數據供應程序。 (Microsoft 為 .NET 框架的 1.1 版提供的供應程序包含在該框架中,不需要單獨下載或安裝。) 一些第三方數據供應程序支持 Oracle 的較早的版本,或者不需要安裝 Oracle 客戶端軟件。 在本文中,我們假設使用 Oracle Data Provider for .NET (ODP.Net),並單獨提供下載。
當安裝了 ODP.NET 和所有要求的 Oracle 客戶端連通性軟件時,就可以開始使用 Visual Studio.Net 進行應用程序開發了。 在開始開發前,請先確認客戶端連通性。 如果您在 VS.Net 所在的計算機上使用 SQL*Plus 能夠與 Oracle 連接,那麼證明您已經正確地安裝和配置了 Oracle 客戶端軟件。
如果您剛接觸 Oracle,那麼請參閱 Oracle Data Provider for .NET 開發人員指南 10g 版本 1 (10.1) 中的“與 Oracle 數據庫連接”部分,以了解 ODP.NET 的背景信息,或參閱 Oracle 數據庫管理員指南 10g 版本 1 (10.1),以了解關於管理 Oracle 數據庫的通用信息。 您還可以查閱“使用 ODP.Net 與 Oracle 數據庫連接”示例代碼“方法”文檔。
在 Visual Studio.Net 中創建工程
在啟動 VS.Net 之後,第一個任務是創建一個工程。 您可以單擊 New Project 按鈕或選擇 File | New | Project...(如下所示)。
圖 1: 在 Visual Studio.Net 中創建一個新工程
出現一個 New Project 對話框。 在對話框左側的 Project Types 下,選擇您的編程語言。 在這個例子中,我們選擇 VB.Net。 在右側的 Templates 下,選擇一個工程模板。 為簡單起見,這裡選擇 Windows Application。
圖 2: 使用 New Project 對話框
您將需要為工程(我們使用 OtnWinApp)和解決方案(我們使用 OtnSamples)指定有意義的名稱。 一個解決方案包含一個或多個工程。 當一個解決方案僅包含一個工程時,許多人對二者使用相同的名稱。
添加引用
因為我們的工程必須與 Oracle 數據庫連接,因此必須添加一個到包含我們選擇的數據供應程序的 dll 的引用。 在 Solution Explorer 內,選擇 References 節點,右鍵單擊並選擇 Add Reference。 或者,您可以轉至菜單欄並選擇 Project,然後選擇 Add Reference。
圖 3: 添加引用
出現 Add Reference 對話框。
圖 4: 選擇 ODP.Net 管理的數據供應程序
從列表中選擇 Oracle.DataAccess.dll,然後單擊 Select 按鈕,最後單擊 OK 按鈕,使您的工程能夠找到 ODP.Net 數據供應程序。
圖 5: 選擇 Oracle Managed Provider 之後的解決方案浏覽器
VB.Net/C# 語句
在添加引用之後,標准的做法是要添加 VB.Net Imports 語句、C# using 語句或 J# import 語句。 從技術上說這些語句不是必要的,但是使用它們可以讓您不需用冗長且完整名稱來引用數據庫對象。
按照慣例,這些語句出現在代碼文件的頂部或頂部附近,在命名空間或類聲明之前。連接字符串和對象 Imports System.Data ' VB.Net
Imports Oracle.DataAccess.ClIEnt ' ODP.Net Oracle managed providerusing System.Data;
// C#
using Oracle.DataAccess.ClIEnt;
// ODP.Net Oracle managed providerimport System.Data.*;
// J#
import Oracle.DataAccess.ClIEnt;
// ODP.Net Oracle managed provider
Oracle 連接字符串和 Oracle 名稱解析是不可分的。 假定我們在 tnsnames.ora 文件中定義了一個數據庫別名 OraDb,如下:
要使用上面所述的在 tnsnames.ora 文件中定義的 OraDb 別名,您需要使用以下語法: OraDb=
(DESCRIPTION=
(ADDRESS_LIST=
(ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521))
)
(CONNECT_DATA=
(SERVER=DEDICATED)
(SERVICE_NAME=ORCL)
)
)
Dim oradb As String = "Data Source=OraDb;User Id=scott;PassWord=tiger;" ' VB.Netstring oradb = "Data Source=OraDb;User Id=scott;PassWord=tiger;";
// C#
不過,您可以修改連接字符串,這樣就不需用 tnsnames.ora 文件。 只需用在 tnsnames.ora 文件中定義別名的語句替換別名即可。
正如您在上面看到的那樣,用戶名和口令是以不加密的文本形式嵌入到連接字符串中的。 這是創建連接字符串的最簡單的方法。 然而,從安全的角度而言不加密文本的方法是不可取的。 而且,您需要了解編譯的 .NET 應用程序代碼僅比不加密文本形式的源代碼文件稍微安全一點。 可以非常簡便的反編譯 .Net dll 和 exe 文件,進而查看原始的不加密文本形式的內容。 (加密實際上是正確的解決方案,但這個主題與我們這裡的討論相差太遠。) 接下來,您必須從連接類中完成一個連接對象的實例化。 連接字符串必須與連接對象關聯。 ' VB.Net
Dim oradb As String = "Data Source=(DESCRIPTION=" _
+ "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521)))" _
+ "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));" _
+ "User Id=scott;PassWord=tiger;"string oradb = "Data Source=(DESCRIPTION=" // C#
+ "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521)))"
+ "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));"
+ "User Id=scott;PassWord=tiger;";
注意,通過將連接字符串傳遞給連接對象的構造器(該構造器進行了重載),連接字符串與連接對象建立關聯。 構造函數的其他重載允許以下這些替代的語法: Dim conn As New OracleConnection(oradb) ' VB.NetOracleConnection conn = new OracleConnection(oradb); // C#
在連接字符串與連接對象建立關聯之後,使用 Open 方法來創建實際的連接。 Dim conn As New OracleConnection() ' VB.Net
conn.ConnectionString = oradbOracleConnection conn = new OracleConnection(); // C#
conn.ConnectionString = oradb;
我們將在稍後介紹錯誤處理。 conn.Open() ' VB.Netconn.Open(); // C#
Oracle DBA 或開發人員很清楚 ORA-12545 的意義,但是最終用戶不清楚。 一種更好的解決方案是添加一條額外的 Catch 語句來捕獲最常見的數據庫錯誤並顯示對用戶友好的消息。
注意上面的代碼示例中的兩條 Catch 語句。 如果沒有捕獲到任何 Oracle 錯誤,那麼將跳過第一條Catch 語句分支,讓第二條 Catch 語句來捕獲其他任何類型的錯誤。 在代碼中,應該根據從特殊到一般的順序對 Catch 語句排序。 在實施了對用戶友好的異常處理代碼之後,ORA-12545 錯誤消息顯示如下: Catch ex As OracleException ' catches only Oracle errors
If InStr(1, ex.Message.ToString(), "ORA-1:", CompareMethod.Text) Then
MessageBox.Show("Error attempting to insert duplicate data.")
ElseIf InStr(1, ex.Message.ToString(), "ORA-12545:", CompareMethod.Text) Then
MessageBox.Show("The database is unavailable.")
Else
MessageBox.Show("Database error: " + ex.Message.ToString())
End If
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
{
MessageBox.Show(ex.Message.ToString());
}
圖 7: 針對 ORA-12545 錯誤的對用戶友好的消息
Finally 代碼將始終執行,而無論錯誤是否發生。 通過在 Finally 代碼塊中加入連接對象的 Close或 Dispose 方法調用,在執行了 Try-Catch-Finally 代碼段之後,數據庫連接將始終關閉。 試圖關閉沒有打開的數據庫連接不會導致錯誤。 例如,如果數據庫不可用,數據庫連接沒有打開,那麼 Finally 代碼塊將試圖關閉不存在的連接。 執行多余的 Close 或 Dispose 是無效的。 只需將一條 Close 或Dispose 方法放到 Finally 代碼塊中,將保證關閉連接。
到整型的顯式轉換顯示如下:
在隱式轉換上,C# 的容錯能力不如 VB.Net。 您必須自己執行顯式轉換: Label1.Text = CStr(dr.Item("deptno")) ' VB.Net integer to string cast
您可以顯式地轉換標量值以及數組。 string deptno = dr.GetInt16("deptno").ToString(); // C#
關閉並清除
可以調用連接對象的 Close 方法或 Dispose 方法來關閉到數據庫的連接。 Dispose 方法調用 Close方法。
作為可選項,C# 提供了一種在連接超出范圍時自動清除連接的特殊語法。 使用 using 關鍵字可啟用這一特性。 conn.Close() ' VB.Net
conn.Dispose() ' VB.Netconn.Close(); // C#
conn.Dispose(); // C#
您可以試驗在上機操作 1(從數據庫中檢索數據)和上機操作 2(增加交互性)中學到的一些概念。 using (OracleConnection conn = new OracleConnection(oradb))
{
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);
}
錯誤處理
Try-Catch-Finally 結構的錯誤處理是 .Net 語言的一部分。 下面是使用 Try-Catch-Finally 語法的一個相對最小的例子:
雖然這種方法將適當地捕獲嘗試從數據庫中獲取數據時發生的任何錯誤,但這種方法對用戶卻不友好。 例如,看看下面這條在數據庫不可用時顯示的消息。 Dim conn As New OracleConnection(oradb) ' VB.Net
Try
conn.Open()Dim cmd As New OracleCommand
cmd.Connection = conn
cmd.CommandText = "select dname from dept where deptno = " + TextBox1.Text
cmd.CommandType = CommandType.TextIf 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
conn.Dispose()
End TryOracleConnection conn = new OracleConnection(oradb); // 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.GetString(0);
}
}
catch (Exception ex) // catches any error
{
MessageBox.Show(ex.Message.ToString());
}
finally
{
conn.Dispose();
}
圖 6: 捕獲到一個 ORA-12545 錯誤,並向用戶顯示。
Oracle DBA 或開發人員很清楚 ORA-12545 的意義,但是最終用戶不清楚。 一種更好的解決方案是添加一條額外的 Catch 語句來捕獲最常見的數據庫錯誤並顯示對用戶友好的消息。
注意上面的代碼示例中的兩條 Catch 語句。 如果沒有捕獲到任何 Oracle 錯誤,那麼將跳過第一條Catch 語句分支,讓第二條 Catch 語句來捕獲其他任何類型的錯誤。 在代碼中,應該根據從特殊到一般的順序對 Catch 語句排序。 在實施了對用戶友好的異常處理代碼之後,ORA-12545 錯誤消息顯示如下: Catch ex As OracleException ' catches only Oracle errors
If InStr(1, ex.Message.ToString(), "ORA-1:", CompareMethod.Text) Then
MessageBox.Show("Error attempting to insert duplicate data.")
ElseIf InStr(1, ex.Message.ToString(), "ORA-12545:", CompareMethod.Text) Then
MessageBox.Show("The database is unavailable.")
Else
MessageBox.Show("Database error: " + ex.Message.ToString())
End If
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
{
MessageBox.Show(ex.Message.ToString());
}
圖 7: 針對 ORA-12545 錯誤的對用戶友好的消息
Finally 代碼將始終執行,而無論錯誤是否發生。 通過在 Finally 代碼塊中加入連接對象的 Close或 Dispose 方法調用,在執行了 Try-Catch-Finally 代碼段之後,數據庫連接將始終關閉。 試圖關閉沒有打開的數據庫連接不會導致錯誤。 例如,如果數據庫不可用,數據庫連接沒有打開,那麼 Finally 代碼塊將試圖關閉不存在的連接。 執行多余的 Close 或 Dispose 是無效的。 只需將一條 Close 或Dispose 方法放到 Finally 代碼塊中,將保證關閉連接。
利用 DataReader 檢索多個值
到目前為止,我們的示例僅說明了如何檢索單個值。 DataReader 可以檢索多列和多行的值。 首先進行多行、單列的查詢:
要獲取列的值,可以使用以零為基數的序號或列名。 序號與查詢中的順序相關。 因而,可以在 VB.Net 中通過使用 dr.Item(2) 或 dr.Item("loc") 來查詢 loc 列的值。 select deptno, dname, loc from dept where deptno = 10
下面是將 dname 和來自上一查詢的 loc 列串連起來的代碼段:
現在我們進行返回多行的查詢: Label1.Text = "The " + dr.Item(1) + " department is in " + dr.Item("loc") ' VB.NetLabel1.Text = "The " + dr.GetString(1) + " department is in " + dr.GetString(2); // C#
要處理從 DataReader 中返回的多行,需要某種類型的循環結構。 此外,需要一個可以顯示多行的控件。 DataReader 是一個僅正向的只讀游標,因此不能將其與可更新或完全可滾動的控件(如 Windows Forms DataGrid 控件)捆綁在一起。 DataReader 與 ListBox 控件兼容,如以下代碼段所示: select deptno, dname, loc from dept
上機操作 3(利用 DataReader 檢索多列和多行)重點介紹了這些概念中的一部分。 While dr.Read() ' VB.Net
ListBox1.Items.Add("The " + dr.Item(1) + " department is in " + dr.Item("loc")) End Whilewhile (dr.Read()) // C#
{
listBox1.Items.Add("The " + dr.GetString(1) + " department is in " + dr.GetString(2);
}
總結
本文向您介紹了使用 VS.Net 編程語言訪問 Oracle 數據庫的過程。 您現在應該能夠連接數據庫並檢索多列和多行。