最近將公司的項目從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的Data Access Objects
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資料開發架構)的Data Access ObjectsAdoHelper來進行相關的資料訪問操作,它採用反射原廠模式,根據系統的配置執行個體化具體的資料訪問類,這裡使用的是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的Data Access Objects
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的資料太少,寫點東西供大家參考一下。