使用 ODP.NET 訪問 Oracle

使用 ODP.NET 訪問 Oracle

作者: Robert P. Lipschutz 和 Gregg D. Harrington

現在 .NET 開發人員可利用 Oracle 的本地數據訪問來改進應用程序的性能。

要想從 Oracle 所支持的企業應用程序獲得預期的響應性能和高級數據庫特性,應該選擇 Oracle 專用的連接而不是一般的數據庫連接。Java 開發人員長期以來一直可以選擇使用 Oracle 提供的專用 API 連接到 Oracle 數據庫,這些 API 擴展了 JDBC 併爲開發人員利用如大型對象 (LOB) 和 Ref Cursor 這樣的 Oracle 高級特性提供了一條途徑。

2002 年 2 月,Microsoft 推出了 Visual Studio .NET 及其三種語言(C# .NET、Visual Basic .NET 和 C++ .NET)以及底層的 .NET 框架。Microsoft .NET 爲建立企業級的桌面、Web 和 client/server 應用程序提供了一個面向對象的開發平臺。不幸的是,使用這些 Microsoft 語言和 .NET 框架的開發人員並沒有一個現成的、專用於 Oracle 的數據庫連接選項。

針對這一問題,Oracle 爲 .NET (ODP.NET) 專門編寫了 Oracle Data Provider,一個用於 Microsoft .NET 環境下的 Oracle 數據庫訪問 API。在本文中,我們將討論 ODP.NET 的用法、特性和對性能的改善。我們發現使用 ODP.NET 確實具有一些顯著的優勢。

ODP.NET 的基本知識

我們在 Microsoft Visual Studio .NET 中使用專用於 Oracle 數據庫連接的 ODP.NET,建立了一個簡單的 C# 應用程序(名爲 dejavu);完整的源代碼位於 列表 1列表 2 中。這一應用程序及其附帶的數據庫將作爲本文所有示例的一個基礎。

數據庫中的每條記錄都包括一個員工的相關信息。這一應用程序使用帶有四個字段的基本表,表 1 對此進行了概述。

您會發現使用 ODP.NET 進行連接或使用基本的 INSERTSELECT 語句相對要容易些;這些任務與 JDBC 或 OLE DB .NET 同類產品非常相似。在編寫 dejavu 的時侯,我們首先執行了一些基本步驟以便可以使用 ODP.NET:

 

  1. 安裝 ODP.NET 軟件。
  2. 在 Visual Studio 的 Solution Explorer 中,添加一個對 Oracle.DataAccess DLL 的引用。
  3. 在 C# 類中,通過在源代碼文件的最上面使用子句(using Oracle.DataAccess.Client;using Oracle.DataAccess.Types; )添加了兩個命名空間(如果您正在使用 Visual Basic .NET 或其他與 .NET 兼容的語言,則添加命名空間的語法可能會略有不同)。

 

一旦可以使用 ODP.NET,就可以執行下面的步驟來完成一個基本的插入操作:

 

  1. 創建和打開一個數據庫連接。
  2. 創建一個命令。
  3. 執行命令。

 

首先,創建和打開一個數據庫連接:

 

OracleConnection dbConn = new OracleConnection(
	"Data Source=" + txtDataSource.Text + ";" + 
	"User Id=" + txtUsername.Text + ";" + 
	"Password=" + txtPassword.Text + ";");
dbConn.Open();

 

然後使用 OracleConnection object 建立對數據庫的一個連接。每當創建該對象的一個實例,就要提供一個連接字符串。OracleConnection 對象使用此字符串定位到相應的 Oracle 數據庫服務器,並進行認證。

在這個例子中,連接字符串有三個連接參數:Data Source、User IdPassword。由於 Oracle Data Provider 使用了 Oracle 調用接口 (OCI),因此 Data Source 將是 Oracle 客戶端軟件所用 tnsnames.ora 文件中的一個有效 TNS 名。User Id 是指定的 Data Source 的有效用戶名。最後,Password 是與所提供的 User Id 相匹配的密碼值。

然後,打開連接,方法是對 OracleConnection 對象調用 Open() 方法, 並創建一個 OracleCommand 對象(該對象與已創建的 OracleConnection 對象相關聯):

 

OracleCommand cmd = new OracleCommand(
	"insert into dejavu_employee values " + 
	"(1, 'Gregg D. Harrington', empty_blob())", dbConn);

 

在創建 OracleCommand 對象的時侯,需要提供帶有兩個參數的構造符。第一個是希望執行的 SQL 命令;這可以是用字符串所寫的任意有效的 SQL 語句。在這個例子中,準備在 DEJAVU_EMPLOYEE 表中插入一個員工。第二個參數是連接到數據庫的 OracleConnection 對象。

最後,執行 OracleCommand 對象:

 

int rows = cmd.ExecuteNonQuery();
ShowInfo(rows + " where added to the 
database");

 

在這個例子中,要在數據庫中插入一行數據,因此我們將使用 ExecuteNonQuery() 方法。在不需要查詢返回任何行時(比如 SQL 的 INSERT、UPDATEDELETE 語句)就可以使用這個方法(使用 ExecuteReader() 用於 SELECT 語句)。ExecuteNonQuery() 方法接收所構造的 SQL 語句,將其送到數據庫執行,並返回操作涉及到的行數。

現在,讓我們來讀取剛剛插入到數據庫中的記錄並向用戶顯示相應的列名。將使用現有的連接和命令目標,而不是建立新的數據庫連接。

要更改 SQL 語句,可根據想要執行的新的 SQL 語句更改 OracleCommand 對象的 CommandText 屬性:

 

cmd.CommandText = "select * from dejavu_employee where " + 
"id = 1";

 

如果想要使用單一的數據庫連接在單一的 OracleCommand 對象中執行多個 SQL 語句,這一技術就特別有用。

下面,執行該命令並讀取結果:

 

OracleDataReader reader = cmd.ExecuteReader();
reader.Read();
ShowInfo("The selected user's name was " + 
	reader["name"].ToString());
dbConn.Close();

 

我們在此看到一個新對象,OracleDataReader。該對象用於讀取一條或多條記錄。在給出的示例中,由於我們是在主關鍵字列中查找一個特定值,因此只返回一條記錄。 ExecuteReader() 返回一個包含生成的一條或多條記錄的 OracleDataReader 對象。

一旦有了 OracleDataReader 對象,就可以獲得想要的數據了 — 在這個例子中,就是姓名這一列。在開始創建 OracleDataReader 的時侯,該對象不是放置在第一條記錄的位置,因此有必要在讀取第一條記錄之前,調用 Read() 方法。 如果之後還有相應的記錄,則 Read()方法會移動到下一條記錄並返回一個爲真的布爾值。

下面,我們要使用 OracleDataReader 對象缺省的字符串數組屬性檢索姓名列的值,傳遞所要列的名稱 — 在這個例子中,就是姓名。最後,我們另外通過對 OracleConnection 對象調用 Close() 方法關閉這個連接。

比較基本的 ODP.NET 類和與之相當的類

用於 ODP.NET 中的基本類與用於其他連接選項中的基本類非常相似。雖然方法名稱和執行有時也會不太一樣,但基本的邏輯都是一樣的。表 2 列出了一些典型的數據庫操作,以及 ODP.NET、OLE DB .NET 和 Oracle 瘦 JDBC 驅動器處理這些操作時用到的對象。

ODP.NET 高級特性

至此我們已經瞭解了一些基本知識,下面讓我們來仔細瞭解使得 ODP.NET 如此強大的一些特性:對 LOB 和 Ref Cursors 的處理。

Oracle LOB、BLOB 和 Bfile。 Oracle LOB 用於存儲各種大型的字符或二進制數據,包括圖片和文本文檔。ODP.NET 提供本地方法檢索和管理 LOB,這樣就改進了處理 LOB 的代碼性能,並使得編寫這一代碼的過程對於開發人員來說變得更爲簡單。

ODP.NET 包含了 OracleBlobOracleClob 對象。這些對象擴展了 System.IO.Stream 對象的功能。這些數據類型使得開發人員用較少的性能開銷,便可實現 LOB 到數據庫的簡單讀寫。

爲了展示 ODP.NET 處理 BLOB (二進制大對象)和 CLOB (字符型大對象)的能力,我們將逐步演示一個簡單的示例。在這個例子中,我們將打開一個照片文件並將其寫入到數據庫中。對於這一過程的前兩步(創建和打開一個數據庫連接,並創建一個命令)使用上面例子所示的相同方式來完成。

下面,我們必須創建一個事務:

 

OracleTransaction trans = dbConn.BeginTransaction();

 

在更新某個 BLOB 的時侯,需要執行完一個事務中的所有操作以便維護數據的完整性(如果正要準備檢索一個 BLOB 數據庫對象,則不需要創建一個事務)。要創建一個本地事務,調用 OracleConnection 對象的 BeginTransaction() 方法。這樣就返回了一個 OracleTransaction 對象,我們將該對象指定給一個變量以備以後使用。

現在,我們打開該記錄並獲得 OracleBlob 對象:

 

cmd.CommandText = "select * from dejavu_employee where " + 
	"id = 1 for update";

OracleDataReader reader = cmd.ExecuteReader();
reader.Read();
OracleBlob lob = reader.GetOracleBlob(2); // ordinal position of blob

 

我們檢索 OracleBlob 對象,並將其注入照片文件中。首先,我們選擇想要的記錄 — 在這個例子中,就是 ID 號爲 1 的員工記錄。由於我們將更新這個 BLOB,所以數據庫的行必須是鎖定的。我們通過在 SELECT 語句中使用更新子句來執行這一操作。

下面,我們打開一個 OracleDataReader 並調用 Read() 方法移動到第一條記錄。最後,我們從 OracleDataReader 中獲得 OracleBlob 對象。這一操作使用 OracleDataReader.GetOracleLob() 方法來完成。

現在,我們就可以將照片放到服務器上了,如列表 3 所示。應用程序會提示用戶輸入想要上載到數據庫中的文件名。對於這個任務,我們在 Visual Studio 表單設計器中創建的一個 OpenFileDialog 對象 (在我們的例子中就是 ofdPicture)。下面,我們用 System.IO.FileStream 對象打開文件,然後繼續從 FileStream 對象中將數據複製到 OracleBlob 對象。後者使用一個 WHILE 循環通過 Read()Write()方法來實現。

使用 OracleClob 對象和使用 OracleBlob 對象是非常相似的。在讀取 CLOB 數據庫對象時,可以使用 OracleClob 對象從數據庫讀取字符型數據。OracleClob 對象覆蓋 Read() 方法,並向開發人員提供使用 CLOB 置入一個字符數組或字符串的方法。

ODP.NET 還提供一個和 OracleBlob 類似的對象,用於從 Oracle 數據庫中讀取 BFile 數據庫。Bfile 是存儲在數據庫之外文件系統中的 LOB,對於存儲特別大的文件非常有用。

Ref Cursor 和存儲過程。可以通過存儲過程打開 Ref Cursor,並通過一個輸出參數將其傳送到應用程序。

要更好地理解 Ref Cursor 在 ODP.NET 中的用法,可以迅速瀏覽列表 4 中的 PL/SQL 代碼。這段代碼根據輸入參數 n_empidn_empid2,從DEJAVU_EMPLOYEE 表中檢索到兩條記錄。然後通過輸出參數 io_cursorio_cursor2 返回 Ref Cursor。

開發人員會發現使用 ODP.NET 編寫 Ref Cursor 非常容易,因爲他們可以使用遊標的輸出參數(如 io_cursorio_cursor2),如同 OracleDataReaders 一樣。要檢索某個 Ref Cursor,需要首先打開一個數據庫連接,如同第一個示例中所示。下面,我們要創建一個命令調用存儲過程並傳遞必要的參數,如列表 5 所示。在這個列表中,我們使用一個已熟悉的對象(即OracleCommand)來調用存儲過程並獲得結果 Ref Cursor。但是,我們使用存儲過程的輸出參數,而不是使用 SQL 語句從數據庫中獲得記錄。

讓我們逐步演示一下列表 5 中的過程。首先,在第 1 行,我們創建一個新的 OracleCommand 對象。但是,我們是通過提供存儲過程的名稱來調用該存儲過程,而不是使用包含 SQL 語句的一個字符串 — 在我們的例子中,就是 curspkg.open_two_cursor。

下面,在第 2 行,我們將 OracleCommand 對象指定爲存儲過程調用,而不是 SQL 語句調用。要指定這個命令是調用存儲過程,必須正確地指定 OracleCommand 對象的 CommandType 的屬性。 CommandType 屬性有很多可接受的值,可以通過 CommandType 枚舉對象訪問這些值。在這個例子中,我們使用 CommandType.StoredProcedure。

第 3 行和第 4 行創建了兩個必需的參數變量以便訪問返回的 Ref Cursor。我們將使用這些變量從數據庫的存儲過程獲得對 Ref Cursor 結果的引用。

第 5 行到第 8 行向 OracleCommand 對象添加四個必需的參數。我們爲 cmd.Parameters.Add() 方法同一行中的兩個 n_empid 參數創建 OracleParameter 對象。

OracleParameter 構造符接收兩個參數。第一個是 PL/SQL存儲過程參數名;這就意味着可以靈活地添加參數,而不是按照聲明存儲過程參數的順序添加。第二個參數是 PL/SQL 存儲過程參數(即 Value 屬性)應該被解釋成的數據類型; Value 參數的可接受類型位於 OracleDbType 枚舉對象中。這兩個構造符參數同時告訴 OracleParameterOracleCommand 對象如何調用存儲過程,以及如何解釋從該存儲過程返回的值。

接下來的步驟

下載 ODP.NET
/global/cn/tech/windows/odpnet

ODP.NET 示例代碼
/global/cn/sample_code/tech/windows/odpnet

參加 ODP.NET iSeminar
oracle.com/iseminars
使用 Oracle Data Provider for .NET

在我們的示例中,我們向 OracleCommand 對象添加了四個 OracleParameter 對象。其中的兩個是 n_empidn_empid2 輸入參數,這兩個參數是 OracleDbType.Int64 類型,用於 open_two_cursor 存儲過程。我們還需要使用 OracleParameter 對象的 Value 屬性,將參數的值分別設爲 1 和 2。

第三個和第四個參數是 open_two_cursor 存儲過程的輸出參數。在這個例子中,就是 io_cursorio_cursor2。對於 io_cursor 參數,我們並不向存儲過程傳遞某個值,而是希望存儲過程能返回一個 Ref Cursor。要處理這一步,我們添加一個 OracleParameter 對象作爲OracleDbType.RefCursor 類型。要讓 OracleCommand 對象知道這是一個輸出參數,需要將 OracleParameter 對象的 Direction 屬性設置爲 ParameterDirection.Output。

下面,執行存儲過程並檢索結果,如列表 6 所示。在該列表的第 1 行,我們對 OracleCommand 對象調用 ExecuteNonQuery() 方法。該方法調用存儲過程並將結果讀回到正確的 OracleParameter 對象。

在第 2 行和第 3 行,我們將返回值拋入到 OracleRefCursor 對象。在 ODP.NET 執行回傳某個值的存儲過程時,會將那些值存儲在與之相關聯的 OracleParameter 對象的 Value 屬性中。

從第 4 行到第 9 行,我們創建 OracleDataReader 對象,將它們設置爲 Ref Cursor 結果,調用 Read() 方法移動到第一個記錄,並從 OracleDataReader 對象中檢索姓名列。

OracleDataReader 對象還包含了一個名爲 NextResult() 的方法,該方法可以與方法 ExecuteReader() 聯合使用,順序地迭代來自存儲過程的所有 Ref Cursor 結果。利用 NextResult() 方法,我們僅需要使用一個 OracleDataReader 對象來訪問所有的 Ref Cursor。然而,這樣做會限制我們順序地訪問 Ref Cursor。訪問多個 Ref Cursor 結果所使用的方法將取決於應用程序的需要。

結論

Oracle Data Provider for .NET提供一個易於使用的、強大的 API,並且與 OLE DB .NET 相比,性能得到巨大的改善。如果希望在 .NET 框架中開發應用程序訪問新的和現有的 Oracle 數據庫,那麼熟悉這一技術會使您的工作更輕鬆。

Robert P. Lipschutz ([email protected]) 是 Thing 7 的總裁,該公司擅長編寫技術白皮書和設計原型,而 Gregg D. Harrington ([email protected]) 是 Thing 7 的高級設計師。

Table 1:DEJAVU_EMPLOYEE table

 

字段 類型 目的
ID NUMBER(10) 保留員工的 ID(這是該數據庫的主關鍵字)
NAME VARCHAR2(32) 保留員工的姓名
PHOTO BLOB 保留員工的照片
BIGNUMBER NUMBER(38,37) 保留一個高精度數字

Table 2:比較幾種常用 Oracle 連接的
基本選項命令

 

操作 ODP.NET OLE DB .NET ORACLE 瘦 JDBC 驅動器
連接到 OracleConnection OleDbConnection OracleConnection database
創建 SQL OracleCommand OleDbCommand OracleStatement commands
讀取數據 OracleDataReader OleDbDataReader OracleResultSet

Microsoft's .NET Framework Data Provider for Oracle

Microsoft 也發佈了一個 Oracle 專用的數據庫驅動器,名爲 .NET Framework Data Provider for Oracle。由於針對 .NET 的 Oracle 數據庫的連接極其重要,因此 Oracle 和 Microsoft 都來處理這一問題就不足爲奇了。正如 Oracle 的 ODP.NET 一樣,Microsoft 的產品比 OLE DB .NET提供更多 Oracle 專用的功能和更好的性能。然而,Microsoft 的提供程序總得來說還是不如 ODP.NET 的功能多。

例如,ODP.NET API 可以支持事務保存點、本地 Ref Cursor 數據類型、代理服務器用戶認證和全球化特性。Microsoft 的解決方案提供了一個 OracleLob 對象,該對象以相同的方式控制所有的 Oracle LOB 數據庫。然而,ODP.NET 對 Oracle BLOB 和 CLOB 數據庫對象都提供特定的對象。這就意味着 ODP.NET 對這些 Oracle 數據類型提供更好的支持。例如,OracleClob 對象可覆蓋 read 方法置入一組字符數組。這一特性在處理 Unicode 和其他多字節字符時特別有用。

Microsoft 的解決方案和 ODP.NET 都比 OLE DB .NET 提供更好的數據庫訪問性能,最顯著的是寫操作和 LOB 操作。Microsoft 計劃將其新版本的 .NET Framework Data Provider for Oracle 包含在 Visual Studio .NET 的下一個發佈版中。

發佈了18 篇原創文章 · 獲贊 3 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章