Oracle數(shù)據(jù)庫上使用VS2010構(gòu)建.NET應(yīng)用程序
在本文中,我將說明構(gòu)建使用 Oracle 數(shù)據(jù)庫的 C# 或 Visual Basic .NET 應(yīng)用程序所涉及到的基本但不可或缺的過程,包括:
如何添加工程引用,以在您的 .NET 工程中支持 Oracle 類庫
如何創(chuàng)建 Oracle 數(shù)據(jù)庫連接字符串
如何使用 Connection、Command 和 DataReader 對象
您將有機會應(yīng)用在三個上機操作實踐中學(xué)到的內(nèi)容,難度從相對簡單逐漸演變到復(fù)雜。如果您已經(jīng)熟悉使用 Visual Studio 2005/2008 構(gòu)建 Oracle 數(shù)據(jù)庫的 .NET 應(yīng)用程序,則會發(fā)現(xiàn)使用 Visual Studio 2010 的體驗非常類似。
要獲得關(guān)于如何保護應(yīng)用程序的信息和實驗,請參見我的文章“在 Oracle 數(shù)據(jù)庫上保護 .NET 應(yīng)用程序”。(另外,有關(guān)涉及一系列 Oracle .NET 應(yīng)用程序生命周期問題的技術(shù)文章,請參見 OTN .NET 開發(fā)人員中心。)
請注意,免費的 Oracle Developer Tools for Visual Studio(可從 OTN 下載)提供了一個 Visual Studio 插件,該插件可以簡化 Oracle 上的 .NET 應(yīng)用程序開發(fā),并使之更加直觀。但在這里我們不討論這個問題;要想了解這方面的更多信息,您可以前往 Oracle Developer Tools for Visual Studio 產(chǎn)品中心。
.NET 數(shù)據(jù)提供程序
除了基本的 Oracle 客戶端連通性軟件,.NET 應(yīng)用程序還需要使用稱為托管數(shù)據(jù)提供程序(其中“托管”指的是代碼由 .NET 框架管理)的工具。數(shù)據(jù)提供程序是指 .NET 應(yīng)用程序代碼和 Oracle 客戶端連通性軟件之間的層。在幾乎所有情況下,最優(yōu)的性能都是通過使用為特定數(shù)據(jù)庫平臺優(yōu)化了的提供程序而不是一般的 .NET OLE DB 數(shù)據(jù)提供程序?qū)崿F(xiàn)的。
Oracle、Microsoft 和第三方供應(yīng)商都提供了針對 Oracle 數(shù)據(jù)庫進行了優(yōu)化的 .NET 數(shù)據(jù)提供程序。Oracle 和 Microsoft 均免費提供自己的 Oracle 數(shù)據(jù)提供程序。Microsoft 針對 .NET Framework 的提供程序已廢棄。Oracle 則繼續(xù)支持和開發(fā)其 .NET 數(shù)據(jù)提供程序 Oracle Data Provider for .NET (ODP.NET)。在本文中,我們將使用 ODP.NET,該提供程序包含在 Oracle Database 中或單獨提供下載。
ODP.NET 提供標(biāo)準(zhǔn)的 ADO.NET 數(shù)據(jù)訪問,同時還提供專用于 Oracle 數(shù)據(jù)庫的特性,如 XML DB、數(shù)據(jù)訪問性能優(yōu)化和真正應(yīng)用集群負載平衡及快速連接故障切換。最新的 ODP.NET 版本 (11.2) 支持連接到 Oracle Database 9i 第 2 版服務(wù)器及更高版本。數(shù)據(jù)庫服務(wù)器可以位于 Windows、Linux、UNIX 或 Oracle 數(shù)據(jù)庫支持的任何其他操作系統(tǒng)平臺上。
安裝 ODP.NET 和 Oracle 客戶端軟件后,即可開始使用 Visual Studio 進行應(yīng)用程序開發(fā)。在開始開發(fā)前,請先確認(rèn)客戶端連通性。如果您在安裝了 Visual Studio 的計算機上能夠使用 Oracle 客戶端軟件(如 SQL*Plus)連接到 Oracle 數(shù)據(jù)庫,則證明您已經(jīng)正確地安裝和配置了 Oracle 客戶端軟件。
如果您是初次接觸 Oracle,請參見 Oracle 數(shù)據(jù)庫兩日速成開發(fā)人員指南 中的“安裝 .NET 產(chǎn)品”一節(jié),其中專門介紹了有關(guān)安裝和配置 ODP.NET 的背景信息;或參見 Oracle 數(shù)據(jù)庫文檔庫,了解有關(guān) Oracle 數(shù)據(jù)庫的一般信息。
在 Visual Studio 2010 中創(chuàng)建工程
現(xiàn)在我們來創(chuàng)建一個用于從 Oracle 數(shù)據(jù)庫中檢索數(shù)據(jù)的 ODP.NET 應(yīng)用程序。然后,我們將了解如何使用 ODP.NET 執(zhí)行錯誤處理,以及如何處理其他數(shù)據(jù)檢索情況。
啟動 Visual Studio 之后,第一個任務(wù)是創(chuàng)建一個工程。可以按如下所示單擊 New Project,也可以選擇 File | New | Project。

圖 1 在 Visual Studio 2010 中創(chuàng)建一個新工程
出現(xiàn) New Project 對話框。在對話框左側(cè)的 Installed Templates 下,選擇您的編程語言。在這個例子中,我們選擇 Visual Basic。在對話框中部,選擇一個工程模板。為簡單起見,我們選擇 Windows Forms Application。

圖 2 使用 New Project 對話框
您將需要為工程名稱(我們使用 OraWinApp)和解決方案名稱(我們使用 OraWinApp)指定有意義的名稱。一個解決方案包含一個或多個工程。當(dāng)一個解決方案僅包含一個工程時,許多人對二者使用相同的名稱。請注意,對話框中有一個下拉列表框,可以在其中指定您要針對的 .NET Framework 版本。如果您是為早期版本的 .NET Framework 編寫應(yīng)用程序,則應(yīng)從該下拉列表中選擇相應(yīng)的版本。單擊 OK 按鈕繼續(xù)。
添加引用
由于我們的工程必須與 Oracle 數(shù)據(jù)庫連接,因此必須添加一個到包含所選數(shù)據(jù)提供程序的 ODP.NET DLL 的引用。在 Visual Studio 右側(cè)的 Solution Explorer 內(nèi),選擇工程名稱,右鍵單擊并選擇 Add Reference。或者,您可以轉(zhuǎn)至菜單欄并選擇 Project,然后選擇 Add Reference。

圖 3 添加引用
出現(xiàn) Add Reference 對話框。選擇 .NET 選項卡。Visual Studio 將構(gòu)造一個列表,其中列出可以添加到工程中的 .NET 組件。這可能需要幾秒鐘的時間。完成后,您可以單擊 Component Name 列按字母順序排列組件列表。
圖 4 選擇適用于 .NET 4 的 ODP.NET 托管數(shù)據(jù)提供程序
ODP.NET 位于 Oracle.DataAccess 組件名下。從列表中選擇 Oracle.DataAccess,然后單擊 OK 按鈕讓工程知道 ODP.NET 數(shù)據(jù)提供程序。一定要選擇正確的版本。由于這是一個 .NET 4 工程,所以在此選擇了 4.112.2.0 版的 Oracle.DataAccess,但您可以選擇以 4 開頭的任意 ODP.NET 版本。
Visual Basic/C# 語句
添加引用之后,標(biāo)準(zhǔn)的做法是添加 Visual Basic Imports 語句或 C# using 語句。從技術(shù)上講,這些語句不是必需的,但通過它們可以讓您無需使用冗長的完全限定名來引用數(shù)據(jù)庫對象。
按照慣例,這些語句出現(xiàn)在代碼文件的頂部或頂部附近,在命名空間或類聲明之前。
- 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 在 Visual Basic 中添加 Imports 語句
連接字符串和對象
Oracle 連接字符串和 Oracle 名稱解析是不可分的。在本文中,我們將使用用戶 ID“hr”和口令“hr”連接到 Oracle 的示例 HR 模式。tnsnames.ora 文件是一個 Oracle 網(wǎng)絡(luò)配置文件,它定義用于建立連接的數(shù)據(jù)庫地址。假定我們在 tnsnames.ora 文件中定義了一個數(shù)據(jù)庫別名 OraDb,如下:
- OraDb=
- (DESCRIPTION=
- (ADDRESS_LIST=
- (ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521))
- )
- (CONNECT_DATA=
- (SERVER=DEDICATED)
- (SERVICE_NAME=ORCL)
- )
- )
OraDb 別名定義客戶端的數(shù)據(jù)庫地址連接信息。要使用上面所述的在 tnsnames.ora 文件中定義的 OraDb 別名,您需要使用以下語法:
- Dim oradb As String = "Data Source=OraDb;User Id=hr;Password=hr;" ' Visual Basic
- string oradb = "Data Source=OraDb;User Id=hr;Password=hr;"; // C#
不過,您可以修改連接字符串,這樣就無需使用 tnsnames.ora 文件。只需使用在 tnsnames.ora 文件中定義別名的語句替換別名即可。本文中的數(shù)據(jù)庫別名特定于我的數(shù)據(jù)庫設(shè)置。(單擊此處獲取更多有關(guān)為數(shù)據(jù)庫創(chuàng)建數(shù)據(jù)庫別名的信息。)
- ' Visual Basic
- Dim oradb As String = "Data Source=(DESCRIPTION=" _
- + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521))" _
- + "(CONNECT_DATA=(SERVICE_NAME=ORCL)));" _
- + "User Id=hr;Password=hr;"
- // C#
- string oradb = "Data Source=(DESCRIPTION="
- + "(ADDRESS=(PROTOCOL=TCP)(HOST=ORASRVR)(PORT=1521))"
- + "(CONNECT_DATA=(SERVICE_NAME=ORCL)));"
- + "User Id=hr;Password=hr;";
正如您在上面看到的那樣,用戶名和口令是以明文方式嵌入到連接字符串中的。這是創(chuàng)建連接字符串的最簡單的方法。然而,從安全的角度而言不加密文本的方法是不可取的。尤其是,您需要了解編譯的 .NET 應(yīng)用程序代碼僅比不加密文本形式的源代碼文件稍微安全一點??梢苑浅:啽愕胤淳幾g .NET DLL 和 EXE 文件,進而查看原始的不加密文本形式的內(nèi)容。(加密實際上是正確的解決方案,但這個主題與我們這里的討論相差太遠。)
接下來,您必須從連接類中完成一個連接對象的實例化。連接字符串必須與連接對象關(guān)聯(lián)。
- Dim conn As New OracleConnection(oradb) ' Visual Basic
- OracleConnection conn = new OracleConnection(oradb); // C#
注意,通過將連接字符串傳遞給連接對象的構(gòu)造器(該構(gòu)造器進行了重載),連接字符串與連接對象建立關(guān)聯(lián)。構(gòu)造函數(shù)的其他重載允許使用以下這些替代的語法:
- Dim conn As New OracleConnection() ' Visual Basic
- conn.ConnectionString = oradb
- OracleConnection conn = new OracleConnection(); // C#
- conn.ConnectionString = oradb;
在連接字符串與連接對象建立關(guān)聯(lián)之后,使用 Open 方法來創(chuàng)建實際的連接。
- conn.Open() ' Visual Basic
- conn.Open(); // C#
我們將在稍后介紹錯誤處理。
Command 對象
command 對象用于指定執(zhí)行的 SQL 命令文本 — SQL 字符串或存儲過程。類似于 connection 對象,它必須從完成其類的實例化,并且它擁有一個重載的構(gòu)造函數(shù)。在本示例中,ODP.NET 將在 departments 表中執(zhí)行 SQL 查詢,并返回 department_id 為 10 的 department_name。
- Dim sql As String = "select department_name from departments where department_id = 10" ' Visual Basic
- Dim cmd As New OracleCommand(sql, conn)
- cmd.CommandType = CommandType.Text
- string sql = " select department_name from departments where department_id = 10"; // C#
- OracleCommand cmd = new OracleCommand(sql, conn);
- cmd.CommandType = CommandType.Text;
使用不同的重載時,語法的結(jié)構(gòu)稍有不同。command 對象有用于執(zhí)行命令文本的方法,我們將在下一部分中講述。不同的方法適用于不同類型的 SQL 命令。
檢索標(biāo)量值
從數(shù)據(jù)庫中檢索數(shù)據(jù)可以通過實例化一個 OracleDataReader 對象并使用 OracleCommand 的 ExecuteReader 方法(它返回一個 OracleDataReader 對象)來實現(xiàn)。通過將列名或以零為基數(shù)的列序號傳遞給 OracleDataReader 可以訪問返回的數(shù)據(jù)。
- Dim dr As OracleDataReader = cmd.ExecuteReader() ' Visual Basic
- dr.Read()
- Label1.Text = dr.Item("department_name") ' 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
C# 開發(fā)人員必須使用存取程序類型的方法來檢索數(shù)據(jù)。有適當(dāng)類型的存取程序用于返回 .NET 本地數(shù)據(jù)類型,其他存取程序用于返回本地 Oracle 數(shù)據(jù)類型,所有這些存取程序都受 C#、Visual Basic 或任何其他 .NET 語言的支持。以零為基數(shù)的序號被傳遞給存取程序,以指定要返回的列。
- OracleDataReader dr = cmd.ExecuteReader(); // C#
- dr.Read();
- label1.Text = dr["department_name"].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
在這個簡化的示例中,department_name 的返回值是一個字符串,它用來設(shè)置標(biāo)簽控件的文本的屬性值(也是一個字符串)。但如果檢索的是 department_id(不是一個字符串),那么將出現(xiàn)數(shù)據(jù)類型不匹配的情況。當(dāng)源數(shù)據(jù)類型與目標(biāo)數(shù)據(jù)類型不匹配時,.NET 運行時將嘗試隱式地轉(zhuǎn)換數(shù)據(jù)類型。有時數(shù)據(jù)類型不兼容,則隱式轉(zhuǎn)換將失敗,從而引發(fā)異常。但即使可以進行隱式轉(zhuǎn)換,使用顯式數(shù)據(jù)類型轉(zhuǎn)換仍比用隱式數(shù)據(jù)類型轉(zhuǎn)換要好。
到整數(shù)的顯式轉(zhuǎn)換顯示如下:
Label1.Text = CStr(dr.Item("department_id")) ' Visual Basic integer to string cast
在隱式轉(zhuǎn)換上,C# 的容錯能力不如 Visual Basic。您必須自己執(zhí)行顯式轉(zhuǎn)換:
label1.Text = dr.GetInt16("department_id").ToString(); // C#
您可以顯式地轉(zhuǎn)換標(biāo)量值以及數(shù)組。
Close 和 Dispose
可以調(diào)用連接對象的 Close 方法或 Dispose 方法來關(guān)閉到數(shù)據(jù)庫的連接。Dispose 方法隱式調(diào)用 Close 方法。
- conn.Close() ' Visual Basic
- conn.Dispose() ' Visual Basic
- conn.Close(); // C#
- conn.Dispose(); // C#
如果您使用 VB 的 Using 關(guān)鍵字或 C# 的 using 關(guān)鍵字,則不必顯式調(diào)用 Close 或 Dispose。
- using (OracleConnection conn = new OracleConnection(oradb)) // C#
- {
- conn.Open();
- OracleCommand cmd = new OracleCommand();
- cmd.Connection = conn;
- cmd.CommandText = "select department_name from departments where department_id = 10";
- cmd.CommandType = CommandType.Text;
- OracleDataReader dr = cmd.ExecuteReader();
- dr.Read();
- label1.Text = dr.GetString(0);
- }
此外,OracleCommand 包括 Dispose 方法;OracleDataReader 包括 Close 方法和 Dispose 方法。關(guān)閉并刪除 .NET 對象可以釋放系統(tǒng)資源,從而確保高效的應(yīng)用程序性能,這在高負載情況下尤為重要。您可以試驗在上機操作 1(從數(shù)據(jù)庫中檢索數(shù)據(jù))和上機操作 2(增加交互性)中學(xué)到的一些概念。
錯誤處理
當(dāng)錯誤發(fā)生時,.NET 應(yīng)用程序應(yīng)當(dāng)適當(dāng)?shù)靥幚礤e誤并通過一條有意義的消息來通知用戶。Try-Catch-Finally 結(jié)構(gòu)的錯誤處理是 .NET 語言的一部分。下面是使用 Try-Catch-Finally 語法的一個相對最小的示例:
- ' Visual Basic
- Try
- conn.Open()
- Dim cmd As New OracleCommand
- cmd.Connection = conn
- cmd.CommandText = "select department_name from departments " _
- + "where department_id = " + TextBox1.Text
- cmd.CommandType = CommandType.Text
- If dr.Read() Then
- Label1.Text = dr.Item("department_name") ' 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 department_name from departments where department_id = " + textBox1.Text;
- cmd.CommandType = CommandType.Text;
- if (dr.Read()) // C#
- {
- label1.Text = dr["department_name"].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.
- }
雖然這種方法將適當(dāng)?shù)夭东@嘗試從數(shù)據(jù)庫中獲取數(shù)據(jù)時發(fā)生的任何錯誤,但這種方法對用戶卻不友好。例如,看看下面這條在數(shù)據(jù)庫不可用時顯示的消息:

- ' Visual Basic
- 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())
- // C#
- 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 錯誤,那么將跳過第一條 Catch 語句分支,讓第二條 Catch 語句來捕獲任何其他非 Oracle 錯誤。在代碼中,應(yīng)該根據(jù)從特殊到一般的順序?qū)?Catch 語句排序。在執(zhí)行完用戶友好的異常處理代碼之后,ORA-12545 錯誤消息顯示如下:
圖 7 ORA-12545 錯誤的用戶友好的錯誤消息
無論是否發(fā)生錯誤,F(xiàn)inally 代碼塊總會執(zhí)行。清除代碼應(yīng)包含在此代碼塊中。如果未使用 Using 或 using,應(yīng)清除 Finally 代碼塊中的連接和其他對象。
利用 DataReader 檢索多個值
到目前為止,我們的示例僅說明了如何檢索單個值。OracleDataReader 可以檢索多列和多行的值。首先進行多列、單行的查詢:
select department_id, department_name, location_id from departments where department_id = 10
本文為簡明起見,我們使用一個表,即 departments 表。要獲取列的值,可以使用以零為基數(shù)的序號或列名。序號與查詢中的順序相關(guān)。因此,可以在 Visual Basic 中通過使用 dr.Item(2) 或 dr.Item("location_id") 來檢索 location_id 列的值。
下面是將 department_name 和來自上一查詢的 location_id 列串連起來的代碼段:
- Label1.Text = "The " + dr.Item("department_name") + " department is in " _
- + dr.Item("location_id") ' VB
- label1.Text = "The " + dr["department_name"].ToString() + " department is in " +
- dr["location_id"].ToString(); // C#
現(xiàn)在我們進行返回多行的查詢:
select department_id, department_name, location_id from departments
要處理從 OracleDataReader 中返回的多行,需要某種類型的循環(huán)結(jié)構(gòu)。此外,需要一個可以顯示多行的控件。OracleDataReader 是一個僅正向的只讀游標(biāo),因此不能將其與可更新或完全可滾動的控件(如 Windows Forms DataGrid 控件)捆綁在一起。OracleDataReader 與 ListBox 控件兼容,如以下代碼段所示:
- While dr.Read() ' Visual Basic
- ListBox1.Items.Add("The " + dr.Item("department_name") _
- + " department is in " + dr.Item("location_id"))
- End While
- while (dr.Read()) // C#
- {
- listBox1.Items.Add("The " + dr["department_name"].ToString() + " department is in " +
- dr["location_id"].ToString());
- }
上機操作 3(利用 OracleDataReader 檢索多列和多行)重點介紹了這些概念中的一部分。
在 Windows x64 上構(gòu)建和運行
在 Windows x64 操作系統(tǒng)上運行 Visual Studio 2010 時,可以使用 Configuration Manager 更改目標(biāo)平臺類型。在 Solution Explorer 中選擇解決方案,右鍵單擊并選擇 Configuration Manager。
圖 8 在 Windows x64 上構(gòu)建時,可以使用 Configuration Manager 更改目標(biāo)平臺類型。
總結(jié)
本文向您介紹了使用 .NET 編程語言訪問 Oracle 數(shù)據(jù)庫的過程。您現(xiàn)在應(yīng)該能夠連接數(shù)據(jù)庫并檢索多列和多行。
上機操作 1:從數(shù)據(jù)庫中檢索數(shù)據(jù) 前提條件是您已經(jīng)創(chuàng)建了一個工程并添加了一個引用(如本文前面部分所述)。
圖 9 包含按鈕和標(biāo)簽控件的表單(上機操作 1)
|
、
上機操作 3:使用 OracleDataReader 檢索多列和多行
|