1. 程式人生 > 其它 >.net訪問PostgreSQL資料庫發生“找不到函式名”的問題追蹤

.net訪問PostgreSQL資料庫發生“找不到函式名”的問題追蹤

    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的資料訪問物件
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框架內建了日誌物件和異常物件,它能夠為你丟擲詳細的錯誤資訊,參看“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的資料訪問物件
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!