文章目錄
PostgreSQL是一個使用廣泛的免費開源的資料庫,與MySQL比較,它更適合複雜的企業計算任務,而MySQL在互連網領域應用更為廣泛,究其原因,可能是PostgreSQL擁有支援最多的資料類型,甚至包括數群組類型,IP地址類型等,可以使用C,SQL,PL/Pgsql,Phython等多種方式編寫強大的自訂函數,因此特別適合處理複雜的計算問題。如果想要將SqlServer資料庫遷移到其它類型的資料庫,PostgreSQL是比較好的選擇。
儘管PostgreSQL使用比較廣泛,但在國內相關資料太少,我們在資料庫遷移的過程中,遇到了不少問題,比如我的上一篇文章PostgreSQL的.NET驅動程式Npgsql中參數對象的一個Bug 中關於“找不到函數名”的問題,解決起來比較“辣手”,可以使用“追蹤”來形容了。本篇繼續對這個問題進行深入探究。
1,問題回顧:
在上一篇文章中說到,有一個PostgreSQL函數 updateattention ,它有一個自訂的函數參數,下面是函數頭:
CREATE OR REPLACE FUNCTION updateattention(dm citext)
RETURNS void AS
$BODY$
--函數體略
參數dm 的類型是citex,一個自訂的資料類型,使用它來作為函數參數或者變數的類型,在進行資料查詢的時候可以不區分大小寫,它的定義是:
CREATE OR REPLACE FUNCTION citext(character)
RETURNS citext AS
'rtrim1'
LANGUAGE internal IMMUTABLE STRICT
COST 1;
ALTER FUNCTION citext(character) OWNER TO postgres;
下面是調用使用C#調用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架構內建了日誌對象和異常對象,它能夠為你拋出詳細的錯誤資訊,參看“PDF.NET的SQL日誌 ”
2,問題聚焦
一開始還以為是函數名大小寫問題,仔細核對後發現沒有問題,然後嘗試對代碼進行仔細排查。
將上面的程式中第6行代碼
para.DbType = DbType.AnsiString;
注釋掉,程式運行通過,懷疑參數類型不能夠設定成AnsiString,設定成下面的方式:
para.DbType = DbType.String;
程式依然運行不通過,拋出上面同樣的錯誤,只有將這行代碼注釋掉才可以允許通過,思索很久仍然沒有結果,於是昨天寫了本文開頭說的那篇文章(PostgreSQL的.NET驅動程式Npgsql中參數對象的一個Bug)。
今天再次將目光聚集在錯誤資訊的函數參數上:
updatefundattention(text)
難道PostgreSQL的資料類型text 對應的.NET程式類型既不是String,也不是AnsiString?
又搜尋了下,在http://npgsql.projects.postgresql.org/docs/manual/UserManual.html 找到了一張資料類型對照表:
Supported data types
Npgsql supports the following data types:
Postgresql Type |
NpgsqlDbType |
System.DbType Enum |
.Net System Type |
int8 |
Bigint |
Int64 |
Int64 |
bool |
Boolean |
Boolean |
Boolean |
Box, Circle, Line, LSeg, Path, Point, Polygon |
Box, Circle, Line, LSeg, Path, Point, Polygon |
Object |
Object |
bytea |
Bytea |
Binary |
Byte[] |
date |
Date |
Date |
DateTime, NpgsqlDate |
float8 |
Double |
Double |
Double |
int4 |
Integer |
Int32 |
Int32 |
money |
Money |
Decimal |
Decimal |
numeric |
Numeric |
Decimal |
Decimal |
float4 |
Real |
Single |
Single |
int2 |
Smallint |
Int16 |
Int16 |
text |
Text |
String |
String |
time |
Time |
Time |
DateTime, NpgsqlTime |
timetz |
Time |
Time |
DateTime, NpgsqlTimeTZ |
timestamp |
Timestamp |
DateTime |
DateTime, NpgsqlTimestamp |
timestamptz |
TimestampTZ |
DateTime |
DateTime, NpgsqlTimestampTZ |
interval |
Interval |
Object |
TimeSpan, NpgsqlInterval |
varchar |
Varchar |
String |
String |
inet |
Inet |
Object |
NpgsqlInet, IPAddress (there is an implicity cast operator to convert NpgsqlInet objects into IPAddress if you need to use IPAddress and have only NpgsqlInet) |
bit |
Bit |
Boolean |
Boolean, Int32 (If you use an Int32 value, odd values will be translated to bit 1 and even values to bit 0) |
uuid |
Uuid |
Guid |
Guid |
array |
Array |
Object |
Array In order to explicitly use array type, specify NpgsqlDbType as an 'OR'ed type: NpgsqlDbType.Array | NpgsqlDbType.Integer for an array of Int32 for example. |
可以看到 資料庫的text 類型是可以對應.net程式的String類型的,看來問題的關鍵的確是函數參數類型問題。
為了驗證這個想法,將函數的參數類型改為Varchar類型:
CREATE OR REPLACE FUNCTION updateattention(dm varchar)
RETURNS void AS
$BODY$
--函數體略
再次運行前面說的.net資料訪問程式,運行通過!
故此得到結論:
PostgreSQL資料庫的函數中使用“自訂資料類型”,在.NET程式可能無法設定正確的DbType,從而出現找不到函數名的錯誤!
3,“靈異現象”分析
前面說,將
para.DbType = DbType.AnsiString;
代碼注釋即可,也就是不對NpgsqlParameter.DbType 設定任何值,那麼DbType的預設值是什麼呢?
在VS2010的“即時視窗”列印了一下未設定值的para.DbType,發現它的值是:
String
由於上一篇文章已經驗證Npgsql的參數對象DbType無論怎麼設定,擷取該屬性值的時候都是String,所以還是無法得知它的預設屬性值是什麼。
於是一個很偶然的念頭出現:
NpgsqlParameter對象的預設值是不是Object類型?
另外我們的函數使用了自訂的citext類型,所以很可能需要使用DbType.Object類型。
重新修改代碼成下面的方式:
//擷取PostgreSQL的Data Access Objects
PWMIS.DataProvider.Data.AdoHelper db = MyDB.GetDBHelperByConnectionName("PostgreSQL");
//擷取PostgreSQL的參數對象
IDataParameter para = db.GetParameter();
para.ParameterName = "@dm";
para.DbType =
DbType.Object;
para.Value = "KF0355";
db.ExecuteNonQuery("updateattention",
System.Data.CommandType.StoredProcedure,
new System.Data.IDataParameter[] { para });
運行程式,正常通過,看來問題找到了,就是它,在PostgreSQL的自訂類型函數參數中,.net程式的預存程序調用參數應該設定成 DbType.Object!