PostgreSQL的.NET驅動程式Npgsql中引數物件的一個Bug
最近將公司的專案從SqlServer移植到PostgreSQL資料庫上來,在呼叫資料庫的儲存過程(自定義函式)的時候,發現一個奇怪的問題,老是報函式無法找到。
先看一個PgSQL儲存過程:
CREATE OR REPLACE FUNCTION updateattention(dm citext) RETURNS void AS $BODY$ DECLARE BEGIN update ZB set gzd=COALESCE(gzd,0)+1 where ZB.dm=$1 ; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION updateattention(citext) OWNER TO postgres;
在PostgreSQL中,函式和儲存過程沒有區別,這裡我們把沒有返回值的函式叫做儲存過程吧,也許表訴的不太準確,還望大蝦指正。
上面定義一個儲存過程updateattention,它有一個自定義型別 citext,用於將字串中型別換成不區分大小寫的型別,它的定義如下:
CREATE OR REPLACE FUNCTION citext(character) RETURNS citext AS 'rtrim1' LANGUAGE internal IMMUTABLE STRICT COST 1; ALTER FUNCTION citext(character) OWNER TO postgres;
下面是呼叫updateattention儲存過程的程式碼:
//獲取PostgreSQL的資料訪問物件 PWMIS.DataProvider.Data.AdoHelper db = MyDB.GetDBHelperByConnectionName("PostgreSQL"); //獲取PostgreSQL的引數物件 IDataParameter para = db.GetParameter(); para.ParameterName = "@dm"; para.DbType = DbType.AnsiString; para.Value = "KF0355"; db.ExecuteNonQuery("updateattention", System.Data.CommandType.StoredProcedure, new System.Data.IDataParameter[] { para });
程式使用PDF.NET(PWMIS資料開發框架)的資料訪問物件AdoHelper來進行相關的資料訪問操作,它採用反射工廠模式,根據系統的配置例項化具體的資料訪問類,這裡使用的是PostgreSQL資料訪問類。
執行該程式,出現下面的錯誤:
PDF.NET AdoHelper 查詢錯誤:
DataBase ErrorMessage:ERROR: 42883: function updatefundattention(text) does not exist
SQL:updatefundattention
CommandType:StoredProcedure
Parameters:
Parameter["@jjdm"] = "KF0355" //DbType=String
PDF.NET框架內建了日誌物件和異常物件,它能夠為你丟擲詳細的錯誤資訊。
如果採用下面的方式呼叫,又沒有問題:
db.ExecuteNonQuery("select * from updateattention(@dm)", System.Data.CommandType.Text, new System.Data.IDataParameter[] { para });
------------------------------------------------------------------------------------
儘管該方式可以作為一種替代方案,但要用select * from 這種方式呼叫儲存過程,總覺得很彆扭,還得找到問題的真正原因。
這個 "function ... does not exist" 的問題很難搜尋,最終在國外找到一篇文章討論類似的問題:
http://pgfoundry.org/forum/forum.php?thread_id=637&forum_id=519
文中有人說,可能是引數的型別轉換問題,但我這裡只是將引數進行了大小寫轉換,應該不會有類似Int32到Int64這類問題。
無賴,只有將呼叫儲存過程的.NET程式程式碼一個一個排查,當註釋掉
para.DbType = DbType.AnsiString;
的時候,程式居然能夠正常執行通過了!
之前也曾經懷疑過是不是DbType的問題,但是當把滑鼠放到VS2010的編輯器中para 物件下面的時候,智慧提示顯示 DbType="{String}".
預設情況下,引數物件的DbType屬性值是
DbType.String
難道
DbType.AnsiString==DbType.String ??
看了一下定義,它們是有區別的,DbType.AnsiString表示非Unicode的變長字串,DbType.String 表示Unicode的變長字串。
一般情況下,ANSI編碼表示當前系統編碼,所以我猜想AnsiString在我的機器上是Gb2312編碼的,查了一下資料庫的編碼,它是UTF-8格式的,難怪難怪,PostgreSQL給我提示找不到 updatefundattention(text) 函式,注意下,實際上這個函式的引數不是text型別的,它實際上應該是 character 型別,PostgreSQL可以定義同名的函式,但函式可以有不同的引數型別,有點像C#的方法過載。
到此,問題似乎解決了,但還沒完:
VS2010的智慧提示有Bug?
第一次有這個念頭我都覺得不可思議,因為以前在VS2008的時候曾經除錯過類似的程式碼,趕緊將上面的.net程式碼中的引數物件換成其它資料庫型別的引數物件試試看:
//獲取PostgreSQL的資料訪問物件
PWMIS.DataProvider.Data.AdoHelper db = MyDB.GetDBHelperByConnectionName("PostgreSQL");
//使用 SqlServer 的引數物件
IDataParameter para = new SqlParameter();
para.ParameterName = "@dm";
para.DbType = DbType.AnsiString;
para.Value = "KF0355";
db.ExecuteNonQuery("updateattention",
System.Data.CommandType.StoredProcedure,
new System.Data.IDataParameter[] { para });
再此將游標放到para.DbType 上,這次提示正確了,是“{AnsiString}”;
將上面的程式碼放到VS2008中再次驗證,智慧提示正確,看來不是VS2010的Bug,呵呵。
故此,得到的結論:
PostgreSQL的.NET資料訪問驅動程式的引數物件DbType屬性存在一個設定成AnsiString之後檢視該屬性的結果卻是String的Bug!
PS:雖然檢視屬性的確有這樣一個Bug,但好像程式內部做了正確的處理,要不我的程式最終是無法執行通過的。
後記
PostgreSQL的.NET資料驅動程式的這個問題引起的問題使得我困擾了2天左右的時間,不得不發帖說明一下這個過程,現在國內有關PostgreSQL的資料太少,寫點東西供大家參考一下。