问题

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. kfifo

    kfifo 的一些伪代码 kfifo_len() out = LOAD fifo->out smp_rmb() len = LOAD fifo->in - out kfifo_in() k ...

  2. export default和export的使用

    export default和export都是用来向外暴露成员 export default 向外暴露的成员可以使用任意的变量来接收,在一个模块中,export default只允许向外暴露一次,可以 ...

  3. iOS安全攻防(一):Hack必备的命令与工具

    转自:http://blog.csdn.net/yiyaaixuexi/article/details/8288077 你的应用正在被其他对手反向工程.跟踪和操作!你的应用是否依旧裸奔豪不防御? 郑重 ...

  4. Java 实践:生产者与消费者

    实践项目:生产者与消费者[经典多线程问题] 问题引出: 生产者和消费者指的是两个不同的线程类对象,操作同一个空间资源的情况. 需求引出: —— 生产者负责生产数据,消费者负责取走数据 —— 生产者生产 ...

  5. 如何编写 maptalks plugin

    前面写过 maptalks plugin ( ArcGISTileLayer ),有读者留言说文章写得太精简,根据文章给出的核心代码没办法写出一个完整的 plugin ( 文中有完整 demo 地址, ...

  6. 【转载】如何在Android中避免创建不必要的对象

    在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用.这其中避免创建不必要的对象是一项重要的方面. Android设备不像PC那样有着足够大的内存,而且单个App ...

  7. Android 布局渲染流程与卡顿优化

    文章内容概要 一.手机界面UI渲染显示流程 二.16ms原则 三.造成卡顿的原因 四.过度绘制介绍.检测工具.如何避免造成过度绘制造成的卡顿 一.手机界面UI渲染显示流程 大家都知道CPU(中央处理器 ...

  8. 跳跃空间(链表)排序 选择排序(selection sort),插入排序(insertion sort)

    跳跃空间(链表)排序 选择排序(selection sort),插入排序(insertion sort) 选择排序(selection sort) 算法原理:有一筐苹果,先挑出最大的一个放在最后,然后 ...

  9. Centos7安装配置----1配置网络

    1.下载镜像安装,选择的是最小安装,设置root用户密码 (此处省略其中步骤,直到安装成功) 2.安装完成后重启,输入用户名密码进入系统 由于此时未配置网络,所以网卡什么的均未获取ip联网 输入ip ...

  10. 面试必备的13道可以举一反三的Vue面试题

    Vue框架部分我们会涉及一些高频且有一定探讨价值的面试题,我们不会涉及一些非常初级的在官方文档就能查看的纯记忆性质的面试题,比如: *   vue常用的修饰符? *   vue-cli 工程常用的 n ...