1. 程式人生 > 其它 >PostgreSQL的.NET驅動程式Npgsql中引數物件的一個Bug

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的資料太少,寫點東西供大家參考一下。