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.

3
  • 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(). Commented Sep 30, 2014 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. Commented Oct 2, 2014 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... Commented Sep 13, 2016 at 17:41

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.