问题

Oracle 官方提供了 dotnet core 驱动,但我们在使用中遇到了精度问题。

复现

以下代码运行数学运算 1/3,无论是 OracleCommand.ExecuteScalar() 还是 OracleDataReader.GetDecimal(0) 均会抛出异常 InvalidCastException: Specified cast is not valid.

var connectionString = "Data Source=localhost/XE;User ID=system;Password=oracle";
using (var connection = new OracleConnection(connectionString)) {
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "select 1/3 from dual"; //InvalidCastException: Specified cast is not valid.
//var scalar = command.ExecuteScalar(); var reader = command.ExecuteReader();
if (reader.HasRows) {
while (reader.Read()) {
//InvalidCastException: Specified cast is not valid.
var value = reader.GetDecimal(0);
}
}
}

排查

精度溢出的本质是数据类型不能完全匹配,以此为出发点查阅文档,得知 Oracle 返回的数据类型与 C# 版本存在不兼容问题,参考如下:

我们了解到该值被映射到了OracleDecimal类型,应使用OracleDataReader.GetOracleDecimal()读取。

var connectionString = "Data Source=localhost/XE;User ID=system;Password=oracle";
using (var connection = new OracleConnection(connectionString)) {
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "select 1/3 from dual";
var reader = command.ExecuteReader();
if (reader.HasRows) {
while (reader.Read()) {
var original = reader.GetOracleDecimal(0);
original.Dump("original"); //available in LINQPad
Console.WriteLine(String.Join(",", original.BinData));
}
}
}

对于一个从 Oracle 驱动获取的值为 1/3 的 OracleDecimal 类型变量 original

  1. (Decimal)original

    抛出异常 OverflowException: Arithmetic operation resulted in an overflow.
  2. Convert.ChangeType(original, TypeCode.Decimal)

    抛出异常 InvalidCastException4: Object must implement IConvertible
  3. Convert.ChangeType(original.Value, TypeCode.Decimal)

    抛出异常,同1,因为对 Value 的访问已经失败
  4. BitConverter.ToDouble(original.BinData, 0)

    不可用,值 2.90435521010196E-144
  5. 使用 MemoryStream + BinaryReader.ReadDecimal() 处理字节数组 original.BinData

    抛出异常IOException: Decimal byte array constructor requires an array of length four containing valid decimal bytes.

分析

由第5条得知,OracleDecimal的字节序列并不是 C# 意义上的 Decimal 字节序列,我们仍然需要借助其本身实现字节序列截断,实现如下:

OracleDecimal ToNativeDecimal(OracleDecimal value) {
var bytes = new Byte[22]; //必须使用长度为22字节,否则无法构造出 OracleDecimal
bytes[0] = 15; //告诉驱动字节长度为 16 = 15+1 位,即 .net 世界里的 decimal 长度
Array.Copy(value.BinData, 1, bytes, 1, 15); //拷贝后续15字节
return new OracleDecimal(bitData); //得得到一个 .net 世界能处理的 OracleDecimal
}

后记

数据类型映射出错导致基于 DataReader 的数据读取实现不再牢靠,基于 DbConnection 实现的数据读取类库如 Dapper 需要进一步扩展点以进行支持。

leoninew 原创,转载请保留出处 www.cnblogs.com/leoninew

Oracle 原生驱动带来的精度问题的分析与解决的更多相关文章

  1. maven官方库中没有oracle jdbc驱动的问题解决

    1.找到可用的oracle jdbs驱动jar包文件,放置到指定目录(可根据实际自定义) D:\jdbc\ojdbc14.jar 2.安装好maven,主要是配置好环境变量 MAVEN_HOME='指 ...

  2. Solaris 10下Qt编译Oracle 10g驱动

    上回书讲到<Oracle 10g在Solaris 10中安装详解>,现在开始用Qt来编译下Oracle 10g驱动吧!这样就可以通过Qt程序联入Oracle数据库了! Oracle的环境变 ...

  3. 解决maven官方库中没有oracle jdbc驱动的问题:Missing artifact com.oracle:ojdbc14:jar:10.2.0.1.0

    最近在整合SSHE项目时,想要添加Oracle驱动包时,Maven的pom.xml总是报Missing artifact com.oracle:ojdbc14:jar:10.2.0.1.0错, 下面我 ...

  4. Qt编译Oracle OCI驱动

    最近使用qt开发了一个访问数据库的工具, 默认使用ODBC驱动注入的方式,后来发现Oracle中ODBC驱动注入经常失败. 后来就想直接使用OCI方式访问,而默认情况下Qt只有Sqlite和ODBC驱 ...

  5. ORACLE odbc驱动相关

    驱动下载 http://www.oracle.com/technetwork/topics/winsoft-085727.html http://www.oracle.com/technetwork/ ...

  6. Oracle Jdbc驱动下载及安装本地maven仓库

    由于二进制许可 binary license的限制,oracle jdbc驱动不能通过共有仓库来获取,所以你可以下载下来添加到自己的本地仓库或私有仓库中. 添加到本地仓库步骤如下: 下载Oracle ...

  7. Confluence 6 下载和安装 Oracle thin 驱动

    基于许可证的考虑,我们不能将 Oracle 的驱动捆绑到 Confluence 中.如果你希望你的 Confluence 能够连接到 Oracle 数据库,你需要: 停止 Confluence. 进入 ...

  8. Oracle JDBC驱动安装到Maven本地仓库

    Oracle JDBC驱动因为授权问题,没有放到Maven的中央仓库里面,当然了,阿里云的镜像也没有了.所以要从Oracle官网下载驱动: 注意下载ojdbc6.jar  因为这个JDK1.8才能用. ...

  9. 分享知识-快乐自己:Maven 无法加载 Oracle 数据库驱动源

    由于Oracle授权问题,Maven3不提供Oracle JDBC driver,为了在Maven项目中应用Oracle JDBC driver,必须手动添加到本地仓库. 手动添加到本地仓库需要本地有 ...

随机推荐

  1. MapReduce Shuffle 和 Spark Shuffle 原理概述

    Shuffle简介 Shuffle的本意是洗牌.混洗的意思,把一组有规则的数据尽量打乱成无规则的数据.而在MapReduce中,Shuffle更像是洗牌的逆过程,指的是将map端的无规则输出按指定的规 ...

  2. SpringCloud的入门学习之Netflix-eureka(Eureka的集群版搭建)

    1.Eureka单机版的话,可能会出现单点故障,所以要保障Eureka的高可用,那么可以进行搭建Eureka的集群版. 高可用的Eureka的注册中心,将注册中心服务部署到多台物理节点上,形成一个集群 ...

  3. vi 上下左右变ABCD乱码解决方法

    CentOS echo "set nocompatible" >> ~/.vimrc source ~/.vimrc debian sudo apt-get remov ...

  4. node设置跨域白名单

    // 判断origin是否在域名白名单列表中 function isOriginAllowed(origin, allowedOrigin) { if (_.isArray(allowedOrigin ...

  5. IP地址网段表示法

    172.12.34.0/25 子网掩码:用于表示IP地址中的多少位用来做主机号.因为"其中值为1的比特留给网络号和子网号,为0的比特留给主机号"(TCP/IP V1). 172.1 ...

  6. JS中的NaN和isNaN,简直是双重人格?

     number数字类型 包括数字和NaN,NaN:not a number 但是它是数字类型的   isNaN的用法:检测当前值是否不是有效数字,返回true代表不是有效数字,返回false是有效数字 ...

  7. swift(四)swift的广义匹配

    //swift的广义匹配 let x = switch x { ...: println("个位数") ...: println("十位数") default: ...

  8. SpringCloud学习笔记(五、SpringCloud Netflix Hystrix)

    目录: Hystrix简介 线程隔离:线程池.信号量 服务降级.服务熔断.请求缓存.请求合并 Hystrix完整流程.Hystrix属性值 注解方式实现Hystrix Hystrix Dashboar ...

  9. 【西北师大-2108Java】第二次作业成绩汇总

    2[西北师大-2108Java]第二次作业成绩汇总 以命令行方式或在Eclipse集成开发环境中编辑.编译.运行第3章示例程序3-1-3-5,结合程序运行结果理解程序代码,每个示例程序从语法.算法两个 ...

  10. 利用java程序构造mysql测试数据

    package com.baidu.mysql;import java.sql.*; public class MysqlJdbc { /** * @param args */ public stat ...