iBatis.NET使用ODP.NET的注意事項
上一篇文章《.NET程序員看Oracle數據類型》總結了一些Oracle的常用數據類型和.NET類型的映射關系,這篇文章討論如何實現它。我使用的數據庫驅動是ODP.NET,數據訪問框架是iBatis.NET。
一、 數值類型:
我把iBatis.NET自帶的數值類型的TypeHandler全部重寫了,因為這里面有這么幾個問題:
1、 ODP.NET不支持SByte、UInt16、UInt32、UInt64這4種類型的參數值。事實上,在ODP.NET的OracleDbType枚舉中,也沒有這4種類型的枚舉;而如果使用System.Data.DbType枚舉中的這4種類型的枚舉,或者不設置參數的OracleDbType屬性和DbType屬性而直接給Value屬性賦值,也會出現異常。
2、 對于輸出參數,如果給參數的OracleDbType屬性進行了賦值,則參數的值將是ODP.NET中定義的OracleDecimal。ODP.NET的Developer’s Guide中是這么說的:
ODP.NET allows applications to obtain an output parameter as either a .NET Framework type or an ODP.NET type. The application can specify which type to return for an output parameter by setting the DbType property of the output parameter (.NET type) or the OracleDbType property (ODP.NET type) of the OracleParameter object. For example, if the output parameter is set as a DbType.String type by setting the DbType property, the output data is returned as a .NET String type. On the other hand, if the parameter is set as an OracleDbType.Char type by setting the OracleDbType property, the output data is returned as an OracleString type. If both DbType and OracleDbType properties are set before the command execution, the last setting takes affect.
問題在于iBatis.NET的數值類型的TypeHandler的實現代碼中,直接使用了Convert類來進行轉換,而OracleDecimal類型并不支持這個轉換。
3、 對于NUMBER類型的字段,即使你確實是為存儲Single/Double類型設置了精度和小數位數,但是讀取時Oracle返回的類型可能是Decimal。ODP.NET的Developer’s Guide中是這么說的:
Certain methods and properties of the OracleDataReader object require ODP.NET to map a NUMBER column to a .NET type based on the precision and scale of the column. These members are:
■ Item property
■ GetFieldType method
■ GetValue method
■ GetValues method
ODP.NET determines the appropriate .NET type by considering the following .NET types in order, and selecting the first .NET type from the list that can represent the entire range of values of the column:
■ System.Byte
■ System.Int16
■ System.Int32
■ System.Int64
■ System.Single
■ System.Double
■ System.Decimal
If no .NET type exists that can represent the entire range of values of the column, then an attempt is made to represent the column values as a System.Decimal type. If the value in the column cannot be represented as System.Decimal, then an exception is raised.
For example, consider two columns defined as NUMBER(4,0) and NUMBER(10,2). The first .NET types from the previous list that can represent the entire range of values of the columns are System.Int16 and System.Double, respectively. However, consider a column defined as NUMBER(20,10). In this case, there is no .NET type that can represent the entire range of values on the column, so an attempt is made to return values in the column as a System.Decimal type. If a value in the column cannot be represented as a System.Decimal type, then an exception is raised.
但是Oracle在判斷使用哪個.NET數值類型時,卻不是完全精確的根據.NET數值類型的范圍來確定的,因此你認為是應該返回Single/Double卻有可能實際返回的類型是Decimal。iBatis.NET的SingleTypeHandler、DoubleTypeHandler、NullableSingleTypeHandler、NullableDoubleTypeHandler的實現代碼中使用了強制類型轉換,則就有可能發生異常。
當然,對于BINARY_FLOAT/BINARY_DOUBLE字段,其返回的類型一定是Single/Double,這可以放心的使用DataReader的GetFloat/GetDouble方法。但是需要在映射中明確指定dbType為BinaryFloat/BinaryDouble。
二、 字符類型:
iBatis.NET中實現的字符類型的TypeHandler,能很好的工作于ODP.NET驅動。
需要說明的只有一點,在使用StringTypeHandler讀寫CLOB/NCLOB字段時,最好在映射中明確指定dbType為Clob/NClob。
三、 日期類型:
iBatis.NET中實現的日期類型的TypeHandler,也存在的一定的問題:
1、 iBatis.NET中的TimeSpanTypeHandler和NullableTimeSpanTypeHandler,是將TimeSpan值的Ticks屬性作為Int64類型的值存儲到數據庫中。而實際上ODP.NET直接支持存儲TimeSpan值,對應的Oracle類型是INTERVAL DAY TO SECOND。當然,iBatis.NET需要支持多種數據庫,為了通用性,這樣處理是合理的。但是我既然確定只使用ODP.NET,就應該根據其實際情況來作出修改。
2、 從.NET 2.0 SP1開始,引入了一個新的DateTimeOffset結構,該結構包括一個 DateTime 值以及一個 Offset 屬性,后者用于確定當前 DateTimeOffset 實例的日期和時間與協調世界時 (UTC) 之間的差值。而ODP.NET中也正好有TIMESTAMP WITH TIME ZONE類型,支持在存儲時間的同時存儲時區。所以為它們兩者建立一個專門的TypeHanlder是合理的。
3、 另外需要說明的是,Oralce中的DATE類型不能存儲小數秒。如果需要存儲DateTime結構中的小數秒,可以使用TIMESTAMP類型,或者TIMESTAMP WITH LOCAL TIME ZONE類型(存儲本地時間戳時)。
四、 二進制類型:
iBatis.NET中實現的二進制類型的TypeHandler,能很好的工作于ODP.NET驅動。
和字符類型類似,在使用ByteArrayTypeHandler讀寫BLOB字段時,最好在映射中明確指定dbType為Blob。
五、 布爾類型:
因為Oracle中不支持布爾類型,所以iBatis.NET中預定義的BooleanTypeHandler也就完全沒有用了。
在上一篇文章中,我提到存儲”Y”/”N”,”T”/”F”,1/0三種方式。我個人比較喜歡的是存儲1/0,原因是如果需要,可以轉成枚舉。所以我自己寫了一個OneZeroBooleanTypeHandler來取代預定義的BooleanTypeHandler作為Boolean類型的默認TypeHandler,另外寫了兩個可選的TrueFalseBooleanTypeHandlerCallback和YesNoBooleanTypeHandlerCallback作為備選。
六、 其他類型:
1. 枚舉類型:
.NET枚舉的基礎類型(UnderlyingType)可以是SByte、UInt16、UInt32、UInt64,基于數值類型中同樣的原因,我把預定義的EnumTypeHandler重寫了。
2. 可序列化的類型:
序列化只能定義為TypeHandlerCallBack,因為沒有一個固定的類型。這里的邏輯,只是在寫入時序列化,讀取時反序列而已。
3. Guid:
Oralce不支持Guid類型,因此預定義的GuidTypeHandler也沒法使用。我重寫了該TypeHanlder,把Guid對象轉換為字符串存儲到數據庫中。
4. XmlDocument:
XmlDocument也是常用的一種數據類型,Oracle也支持Xml文檔的存儲,但是和我們這里說的不是一回事。這里的做法是把XmlDocument轉換為字符串存入CLOB/NCLOB,或轉換為字節數組存入BLOB。
轉換的方法也很簡單,使用StringReader/StringWriter可以與字符串轉換,使用MemoryStream可以和字節數組轉換。
照例用表格總結一下:
|
.NET類型 |
Oracle類型 |
OracleDbType (必須在映射中指定用粗體表示) |
TypeHandler (自定義用粗體表示) |
|
Byte |
NUMBER(3) |
Byte |
ByteTypeHandler NullableByteTypeHandler |
|
SByte |
NUMBER(3) |
Byte或Int16(根據數值范圍) |
SByteTypeHandler NullableSByteTypeHandler |
|
Int16 |
NUMBER(5) |
Int16 |
Int16TypeHandler NullableInt16TypeHandler |
|
UInt16 |
NUMBER(5) |
Int16或Int32(根據數值范圍) |
UInt16TypeHandler NullableUInt16TypeHandler |
|
Int32 |
NUMBER(10) |
Int32 |
Int32TypeHandler NullableInt32TypeHandler |
|
UInt32 |
NUMBER(10) |
Int32或Int64(根據數值范圍) |
UInt32TypeHandler NullableUInt32TypeHandler |
|
Int64 |
NUMBER(20) |
Int64 |
Int64TypeHandler NullableInt64TypeHandler |
|
UInt64 |
NUMBER(20) |
Int64或Decimal(根據數值范圍) |
UInt64TypeHandler NullableUInt64TypeHandler |
|
Single |
FLOAT(24) |
Single |
SingleTypeHandler NullableSingleTypeHandler |
|
Single |
BINARY_SINGLE |
BinaryFloat | |
|
Double |
DOUBLE PRECISION |
Double |
DoubleTypeHandler NullableDoubleTypeHandler |
|
Double |
BINARY_DOUBLE |
BinaryDouble | |
|
Decimal |
NUMBER |
Decimal |
DecimalTypeHandler NullableDecimalTypeHandler |
|
Char |
VARCHAR2(1 Char) NVARCHAR2(1) |
Char NChar |
CharTypeHandler NullableCharTypeHandler |
|
String |
VARCHAR2(n char) NVARCHAR2(n) CLOB NCLOB |
Varchar2 NVarchar2 Clob NClob |
StringTypeHandler |
|
DateTime |
DATE TIMESTAMP TIMESTAMP WITH LOCAL TIME ZONE |
Date TimeStamp TimeStampLTZ |
DateTimeTypeHandler NullableDateTimeTypeHandler |
|
DateTimeOffset |
TIMESTAMP WITH TIME ZONE |
TimeStampTZ |
DateTimeOffsetTypeHandler NullableDateTimeOffsetTypeHandler |
|
TimeSpan |
INTERVAL DAY TO SECOND |
IntervalDS |
TimeSpanTypeHandler NullableTimeSpanTypeHandler |
|
Byte[] |
RAW BLOB |
Raw Blob |
ByteArrayTypeHandler |
|
Boolean |
NUMBER(1)(1/0) 或INTEGER(便于擴展) |
Int32 |
OneZeroBooleanTypeHandler NullableOneZeroBooleanTypeHandler |
|
Boolean |
VARCHAR2(1)(’T’/’F’) |
Varchar2 |
TrueFalseBooleanTypeHandlerCallback NullableTrueFalseBooleanTypeHandlerCallback |
|
Boolean |
VARCHAR2(1)(’Y’/’N’) |
Varchar2 |
YesNoBooleanTypeHandlerCallback NullableYesNoBooleanTypeHandlerCallback |
|
枚舉類型 |
NUMBER(n)(存儲基礎類型的值) 或INTEGER(便于擴展) |
Byte/Int16/Int32/Int64 |
EnumTypeHandler |
|
枚舉類型 |
VARCHAR2(n char)或NVARCHAR2(n)(存儲常量名稱) |
Varchar2/Nvarchar2 | |
|
可序列化類型 |
BLOB |
Blob |
SerializableTypeHandlerCallBack |
|
Guid |
Varchar2(38)(ToString()方法) |
Varchar2 |
GuidTypeHandler NullableGuidTypeHandler |
|
XmlDocument |
NCLOB/CLOB(Save到TextWriter) |
NClob/Clob |
XmlDocumentTypeHandler |
|
XmlDocument |
BLOB(Save到Strema) |
Blob |
參考資料:
1、 iBatis.NET源代碼。我下載的版本是513437。
2、 《Oracle11g Data Provider for .NET (11.1.0.6.20) Developer's Guide .pdf》。
浙公網安備 33010602011771號