- 1、.Net for Oracle 常見數據庫驅動
- 2、ODP.NET 常見問題分析
- 2.1、參數化問題
- 2.2、方法調用問題
- 2.3、取不到存儲過程的輸出參數值
- 3、總結
網上有大量諸如 C#/.Net 連接 Oracle 的幾種方式之類的帖子,無非也就是介紹幾種驅動,大部分內容還雷同。曾經我只是想上網找一個適合手頭上項目訪問 Oracle 的方法,結果卻迷失在浩瀚如煙的相似帖子中望洋興嘆。隨着時間的推移,我逐漸理清了這里面的關系,本文將按我的理解介紹幾個常見的 .Net for Oracle 數據庫驅動,並重點分析我本人在使用 ODP.NET 中遇到的 3 類問題。
1、.Net for Oracle 常見數據庫驅動
數據庫驅動是由數據庫廠商或第三方數據庫驅動開發商為了某種開發語言(如 C#、Java)能夠方便的訪問某種數據庫(如 SQL Server、Oracle)而提供的接口程序。程序代碼一般都是通過數據庫驅動來訪問數據庫,其實 .Net 連接 Oracle 的方式也就一種——先選定一個數據庫驅動,然后按這個驅動的調用方法來連接數據庫。本節將分類逐一介紹 ODBC、OLE DB、ADO.NET、ODAC、ODP.NET、dotConnect for Oracle 共 6 個數據庫驅動。
1.1、微軟提供的驅動
微軟的數據庫訪問技術由來已久,可選的驅動也比較多。如果數據庫用的是 SQL Server,那么微軟提供的數據庫驅動將是不二選擇,但如果用的是 Oracle,情況就相對復雜了,下文會詳細說明。
ODBC:即開放數據庫互連(Open Database Connectivity),它定義了訪問數據庫 API 的一個規范,是 Microsoft 提出的數據庫訪問接口標准,這些 API 獨立於不同廠商的 DBMS,也獨立於具體的編程語言。但這是一種非常古老的訪問技術,本人工作中從未見人用 ODBC,我自己也從沒用過,同時也不建議讀者朋友們用這種方式。本人從《Oracle 數據庫開發指南》中找到《Using the Oracle ODBC Driver》,有興趣的讀者可通過此文稍微了解下。
OLE DB:即數據庫鏈接和嵌入對象(Object Linking and Embedding Database),是微軟提出的基於 COM 思想且面向對象的一種技術標准,目的是提供一種統一的數據訪問接口來訪問各種數據源。OLE DB 不僅包括微軟資助的標准數據接口開放數據庫互連(ODBC)的結構化查詢語言(SQL)能力,還具有訪問其他非 SQL 數據類型的能力。OLE DB 比 ODBC 要先進的多,著名的 ADO 就是基於 OLE DB 開發的,有興趣讀者朋友可以看看《Provider for OLE DB Developer's Guide》。在本人工作這幾年里倒是沒見人用過它,我此前也沒用過,下例我第一次嘗試用 OLE DB 驅動連接 Oracle 數據庫的代碼,其中連接字符串里的Provider用於指定驅動提供商,可選項有MSDAORA和OraOLEDB.Oracle,而Data Source用於指定要連接的數據庫,一般寫主機字符串即可。
using System;
using System.Data.OleDb;
namespace OracleSeries
{
class ConnectOracleByOleDb
{
static void Main(string[] args)
{
string connectionString = @"Provider=OraOLEDB.Oracle;Data Source=ORCL_127.0.0.1;User ID=demo;PassWord=test;";
using (OleDbConnection conn = new OleDbConnection(connectionString))
using (OleDbCommand cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT SYSDATE FROM DUAL";
using (OleDbDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Console.WriteLine("服務器時間: {0}", dr[0]);
}
}
}
Console.ReadKey();
}
}
}
寫這段代碼的過程中我遇到一個問題:剛開始我選擇的驅動是 MSDAORA,一運行代碼就報“未在本地計算機上注冊“MSDAORA”提供程序”,后來發現只要把項目的目標平台改為 x86 就好了,於是乎我換成 OraOLEDB.Oracle 又試了一下,不改目標平台也可以運行,甚是奇怪,由於平常從來不用它,我也就沒深入去研究這個問題了。
ADO.NET:可能每個 .Net 程序都用過 ADO.NET,但大部分應該是用它來連接 SQL Server。在早期的 .Net 版本中,微軟提供了對 Oracle 的支持,但最終微軟還是停止了更新。本人在微軟官網上找到了一個說明——《Oracle 和 ADO.NET》。其實在 .Net 4.0 中我們可以通過手動添加System.Data.OracleClient引用的方式來繼續使用 ADO.NET 連接 Oracle,且 VS 會提示相關類型已過時,但代碼依然可以照常運行。示例:
using System;
using System.Data.OracleClient;
namespace OracleSeries
{
class ConnectOracleByAdoNet
{
static void Main(string[] args)
{
string connectionString = @"Data Source=ORCL_127.0.0.1;User ID=demo;PassWord=test;";
using (OracleConnection conn = new OracleConnection(connectionString))
using (OracleCommand cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT SYSDATE FROM DUAL";
using (OracleDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Console.WriteLine("服務器時間: {0}", dr[0]);
}
}
}
Console.ReadKey();
}
}
}
1.2、甲骨文提供的驅動
甲骨文提供了一個針對 Windows 和 .NET 的數據庫訪問組件——ODAC(Oracle Data Access Components),它包含了一系列的數據訪問驅動程序和工具,如:Oracle Data Provider for .NET(ODP.NET)、Oracle Provider for OLE DB、Oracle ODBC Driver、Oracle Developer Tools for Visual Studio、Oracle SQL*Plus、Oracle Instant Client 等。本節將重點討論 ODP.NET,我覺得它應該是當下 .Net 程序訪問 Oracle 的最佳選擇。
ODP.NET 先后提供了兩個訪問 Oracle 的 DLL 文件,分別是非托管的Oracle.DataAccess.dll和托管的Oracle.ManagedDataAccess.dll。ODP.NET 是可以單獨使用的,無需安裝 ODAC 或其它任何組件,只需要將 DLL 文件拷貝到項目中引用一下即可。其中非托管的 DLL 得裝 Oracle 客戶端才能訪問 Oracle,而托管的 DLL 免裝客戶端。它們的命名空間分別是Oracle.DataAccess.Client和Oracle.ManagedDataAccess.Client,但 API 命名貌似完全一致,至少我目前還沒發現有任何不同。如果你想進一步了解 ODP.NET,請看官方說明——《Oracle Data Provider for .NET》。
注意:本人強烈不建議使用非托管的 DLL,首先它沒有客戶端就不能訪問 Oracle,像我這種喜歡用簡易客戶端的人就不能用它,否則項目都無法調試了;更加令人惡心的是它還有兩個版本限制,一個是操作系統的版本限制,32位和64位得區別對待,另一個是 DLL 文件版本的限制,哪怕是小版本的區別都有可能導致程序集加載失敗,盡管可以通過 DLL 重定向來解決,但比較繁瑣。我不太喜歡裝一大堆東西,所以有時候我感覺Oracle.DataAccess.dll簡直就是個垃圾!如果項目比較老,在還沒有 Oracle.ManagedDataAccess.dll的時候不慎用了Oracle.DataAccess.dll,想切換過來也是非常容易的,只需要替換 DLL 文件和命名空間即可。調用托管 DLL 的示例:
using System;
using Oracle.ManagedDataAccess.Client;
namespace OracleSeries
{
class ConnectOracleByOdpNet
{
static void Main(string[] args)
{
string connectionString = @"Data Source=ORCL_127.0.0.1;User ID=demo;PassWord=test;";
using (OracleConnection conn = new OracleConnection(connectionString))
using (OracleCommand cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT SYSDATE FROM DUAL";
using (OracleDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Console.WriteLine("服務器時間: {0}", dr[0]);
}
}
}
Console.ReadKey();
}
}
}
細心的讀者應該能夠發現,除了命名空間不同之外,其它代碼與 ADO.NET 的示例完全一樣,事實上它倆的 API 區別也的確是微乎其微!可能一開始 Oracle 就考慮到讓 ODP.NET 與 ADO.NET 兼容吧,這樣也方便舊項目從 ADO.NET 切換到 ODP.NET。
1.3、其它廠商提供的驅動
數據庫驅動這種程序,一般都是數據庫廠商或語言廠商做的比較好,但依然有部分第三方廠商做的數據庫驅動也還不錯。譬如 Devart 公司推出的商業版數據庫驅動 dotConnect for Oracle 等。
dotConnect for Oracle:原名 OraDirect,完全基於 ADO.NET 開發的,不用裝 Oracle 客戶端,而且性能非常好——《dotConnect for Oracle Performance》,應該是 .Net for Oracle 第三方驅動中做的最好的。本人待過的兩個公司都不用這個驅動,估計有免費的 ODP.NET 公司就都不願掏錢了吧!我自己目前也沒用過,有興趣的讀者可自行研究。官網:http://www.devart.com/dotconnect/oracle/。
2、ODP.NET 常見問題分析
2.1、參數化問題
可能每個程序員都知道“參數化查詢”是怎么回事兒,在 ODP.NET 中表示參數的類是 OracleParameter,用法與 ADO.NET 中提供的基本一致,但依然有幾個小問題需要稍微注意下,具體請看下文。
參數前綴問題:一般參數化時所有的 SQL 參數都得加一個特殊的符號前綴。SQL Server 和 SQLite 的參數前綴是@,如@Name;MySQL 的參數前綴是?,如?Name;Oracle 的參數前綴是:,如:Name;而 Access 的所有參數都直接用?來表示(因此需要按照列的出現順序來給參數賦值)。
用 PLSQL 定義並訪問變量user_id的示例:
DECLARE
user_id NUMBER(10);
BEGIN
DELETE FROM t_user t WHERE t.user_id=:user_id;
END;
第一次用 ODP.NET 中的 OracleParameter 時,我習慣性的寫成:new OracleParameter("@user_id", userId);,結果不行。想到 PLSQL 里訪問變量得用:,就連賦值都得用:開頭——:=,於是我就想當然的寫成:new OracleParameter(":@user_id", userId);,試了一下報“ORA-01745: 無效的主機/綁定變量名”。實在試不出來了就查了下資料,正確寫法示例:
new OracleParameter(":user_id", userId); // 標准寫法
new OracleParameter("user_id", userId); // 簡易寫法
有一次我偶然寫成這樣:new OracleParameter(":_user_id", userId);,報了個“ORA-00911: 無效字符”,明明 userId 的值就是個數字,為啥會報無效數字呢?把字母 u 前面的_去掉就好了,當時真的是百思不得其解啊!后來我才發現,原來_是 Oracle 中的轉義字符。
變量綁定問題:默認情況下 ODP.NET 要求程序中參數順序必須與 PLSQL 語句中的參數順序完全一致,假如要執行如下語句:
UPDATE t_user t SET t.birthday=:birthday WHERE t.user_name=:user_name AND t.gender=:gender;
那么參數數組中的參數就必須按 birthday->user_name->gender 的順序定義和賦值,其它順序都不行,示例:
OracleParameter[] ps =
{
new OracleParameter("birthday", OracleDbType.Date),
new OracleParameter("user_name", OracleDbType.Varchar2,20),
new OracleParameter("gender", OracleDbType.Int32)
};
ps[0].Value = birthday;
ps[1].Value = userName;
ps[2].Value = gender;
在 ODP.NET 中對參數個數的要求也極為嚴格,必須與 PLSQL 語句中的參數占位符個數相同。假如某個參數在 PLSQL 語句中出現多次,那么程序中也得多次定義和賦值,假如要執行如下語句:
UPDATE t_user t SET t.user_name=:user_name WHERE t.user_name=:user_name;
正確的參數數組定義如下:
OracleParameter[] ps =
{
new OracleParameter("user_name", OracleDbType.Varchar2,20),
new OracleParameter("user_name", OracleDbType.Varchar2,20)
};
ps[0].Value = userName;
ps[1].Value = userName;
其實有個綁定變量的小技巧,就是把 OracleCommand 實例的 BindByName 屬性值設置為 true。這樣參數順序和參數個數就沒必要和 SQL 語句中的完全一致了。示例:
OracleCommand cmd = new OracleCommand();
cmd.BindByName = true; // 相信你看了這個屬性的名字之后就知道是怎么回事兒了吧
2.2、方法調用問題
ExecuteNonQuery() 方法的返回值:一般 ExecuteNonQuery 方法的返回值就是執行 Command 命令中 SQL 語句后,數據庫中受影響的數據行數。但如果執行的是存儲過程,你會發現無論數據庫中數據是否受影響,ExecuteNonQuery 方法的返回值都是 -1。其實這是數據庫里的set nocount on導致的,ExecuteNonQuery 方法的返回值有幾個規律如下:
- 1、對於 UPDATE、INSERT 和 DELETE 語句,返回值為該命令所影響的行數。
- 2、對於 CREATE TABLE 和 DROP TABLE 語句,返回值為 0。
- 3、對於其他所有類型的語句,返回值為 -1。
DataTable.Load() 方法裝載不了閱讀器中的數據:本人曾遇到執行 OracleCommand 對象的 ExecuteReader() 方法之后,得到了一個帶有數據的 OracleDataReader 對象 reader,能在 VS 中監視到 reader 里面有一行數據,reader.Read() 的返回值也是 true,但調用 Load() 方法之后 DataTable 對象中卻依然是空的,包含表結構,但就是沒有數據,當時那個郁悶啊!
后來仔細測試發現,把 reader.Read() 去掉就能拿到 reader 里面哪行數據了。應該是 Read() 之后游標移動到第二行,結果就拿不到第一行的數據了。其實這並不是 ODP.NET 的問題,而是我自己沒搞清楚 Read() 是怎么回事兒,但程序員在學習使用新技術遇到問題時,往往會先懷疑這個技術可能有問題,而不會先懷疑自己過去的認知,但基礎牢固、理解深入的人就很少犯這種錯了,可見對程序員來說基礎是非常重要的!
2.3、取不到存儲過程的輸出參數值(類型為變長字符串VARCHAR2)
- 可能原因1:沒有指定輸出參數的大小,將得到 object{Oracle.ManagedDataAccess.Types.OracleString} 類型的值 {null}。
我曾在 ODP.NET 中調用帶 VARCHAR2 類型輸出參數的存儲過程,結果卻取不到存儲過程里輸出參數的值,得到是一個實際類型為 Oracle.ManagedDataAccess.Types.OracleString 的 object 類型的 null,折騰了老半天也還是不行。於是我請教了在公司工作了十多年的技術專家,對話大致如下:
我:“我用 ODP.NET 調用了一個帶輸出參數的存儲過程,存儲過程和 C# 代碼都沒有語法錯誤,而且輸出參數也有值,但代碼里就是取不到值,可能是啥情況啊?”
專家:“你有在 C# 代碼里指定輸出參數的長度嗎?”
我:“額,沒有,有這個必要嗎?”。我特地翻看了一下之前的代碼,的確是沒有指定長度。我寫代碼定義長度,向來都是以數據庫中的長度為參照,因為在數據庫里存儲過程的參數不允許定義長度,否則會報語法錯誤,所以此處我代碼也與數據庫中保持一致了。
專家:“我們之前也遇到過這個問題,好像是必須加的,否則就取不到值,要不你加上試試看?”
我:“好吧”。我將信將疑的照做之后果然 OK 了!
- 可能原因2:輸出參數的值是空字符串,也會得到和第 1 種情況一樣的值,在代碼中 ToString() 后會變成 "null"。
如果返回值是一個空字符串,程序里得到的值也是一個 null。我的理解是:因為 Oracle 把空字符串做了與 null 值等同的處理,所以存儲過程返回參數實際返回的值應該是 null 值,所以程序里也會得到 null。
3、總結
本文對常見 .NET 訪問 Oracle 數據庫的驅動做了個歸類和總結,同時也對我在使用 ODP.NET 過程中遇到的 3 類問題做了分析。如果你在做基於 Oracle 的 .NET 開發,那么我強烈的推薦你使用 ODP.NET,因為它是免費驅動中做的最好的,而且它的維護者是 Oracle 親爹。
本文鏈接:http://www.cnblogs.com/hanzongze/p/oracle-odp_net.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!本人初寫博客,水平有限,若有不當之處,敬請批評指正,謝謝!
