5

The ODP.NET provider raises an exception in IDataReader.GetValue()/GetValues() if the column type is NUMBER(x,y) such that it will overflow all .NET numeric types. So Dapper is unable to map such a column to a POCO property.

I have an Oracle stored procedure that uses a REF CURSOR output parameter to return 3-column records. Fundamentally all 3 are NUMBER(something), but the ODP.NET Oracle managed provider seems to decide what ODP.NET or .NET type to turn them into.

I've been having problems with Dapper's Query() mapping records from this sproc into POCOs. Perhaps it actually isn't my fault, for once - it seems when a column comes across as an ODP.NET type instead of a .NET type, Dapper fails. If I comment an offending column out of my POCO, everything works.

Here's a pair of rows to illustrate:

--------------------------------------------------------------------
RDWY_LINK_ID           RLC_LINK_OSET          SIGN                   
---------------------- ---------------------- ---------------------- 
1829                   1.51639964279667746989761971196153763602 1 
14380                  578.483600357203322530102380288038462364 -1 

The first column is seen in .NET as int, the second column as type OracleDecimal, and the third as decimal. The second one is the problem.

For example, removing Dapper for the moment and using vanilla ODP.NET to access these records thusly indicates the problem:

int linkid = (int)reader.GetValue(0);
decimal linksign = (decimal)reader.GetValue(2);
//decimal dlinkoffset = (decimal)reader.GetValue(1); //**invalid cast exception at at Oracle.ManagedDataAccess.Client.OracleDataReader.GetDecimal(Int32 i)**
//object olinkoffset = reader.GetValue(1); //**same**
//decimal dlinkoffset = reader.GetDecimal(1); //**same**
//object[] values = new object[reader.FieldCount];
//reader.GetValues(values); //**same**
OracleDecimal linkoffset = (OracleDecimal)reader.GetProviderSpecificValue(1); //this works!
double dblinkoffset = reader.GetDouble(1); //interesting, this works too!
//decimal dlinkoffset = linkoffset.Value; //overflow exception
dblinkoffset = linkoffset.ToDouble(); //voila

What little browsing and breakpointing I've done in Dapper's SqlMapper.cs file shows me that it is extracting data from the reader with GetValue()/GetValues(), as above, which fails.

Any suggestions how to patch Dapper up? Many thanks.

UPDATE:

Upon reflection, I RTFMed: Section 3, "Obtaining Data from an OracleDataReader Object" of the Oracle Data Provider for .NET Developer’s Guide which explains. For NUMBER columns, ODP.NET's OracleDataReader will try a sequence of .NET types from Byte to Decimal to prevent overflow. But a NUMBER may still overflow Decimal, giving an invalid cast exception if you try any of the reader's .NET type accessors (GetValue()/GetValues()), in which case you have to use the reader's ODP.NET type accessor GetProviderSpecificValue(), which gives you an OracleDecimal, and if it overflows a Decimal, its Value property will give you an overflow exception and your only recourse is to coerce it into a lesser type with one of OracleDecimal's ToXxx() methods.

But of course the ODP.NET type accessor is not part of the IDataReader interface used by Dapper to hold reader objects, so it seems that Dapper, by itself, is Oracle-incompatible when a column type will overflow all .NET types.

The question remains - do the smart folk know how to extend Dapper to handle this. It seems to me I'd need an extension point where I could provide implementation on how to use the reader (forcing it to use GetDouble() instead of GetValue(), or casting to OracleDataReader and calling GetProviderSpecificValue()) for certain POCO property or column types.

MC5
  • 293
  • 3
  • 11
  • Hmm. I don't think there's going to be a nice way to do this. Fundamentally, the provider-specific types can't be converted (safely) to .NET types, since there's not enough precision available. It would be nice if you could have properties on your POCO of type `OracleDecimal`, but this only works in a generic fashion if the `GetValue` method actually returns a value of that type instead of throwing an exception. You'll note that `IDataReader` (which is what Dapper is using internally, because that's what ADO.NET exposes via `ExecuteReader()`) doesn't even *have* `GetProviderSpecificValue()`. – Cameron Sep 30 '14 at 22:28
  • We *do* already do some sneaky things if we detect oracle. I could perhaps *look at* this one, but my problem then is testing it: I'm not an Oracle expert. This isn't trivial, though. It is very frustrating that GetValue fails. That's basically Oracle sticking 2 fingers up to the world. – Marc Gravell Oct 02 '14 at 13:04
  • I know this is over a year old now but did you happen to find anything to work around this? My only other consideration is to modify the Oracle lib to do some work around... – Thnesko Sep 13 '16 at 17:41

1 Answers1

1

To avoid this issue i used:

CAST(COLUMN AS BINARY_DOUBLE)

or TO_BINARY_DOUBLE(COLUMN)

In the Oracle types listed here it's described as:

64-bit floating point number. This datatype requires 9 bytes, including the length byte.

Most of the other number types used by Oracle are 22 bytes max, so this is as good as it gets for .NET

Thnesko
  • 108
  • 2
  • 4