问题

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

复现

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

  1. var connectionString = "Data Source=localhost/XE;User ID=system;Password=oracle";
  2. using (var connection = new OracleConnection(connectionString)) {
  3. connection.Open();
  4. var command = connection.CreateCommand();
  5. command.CommandText = "select 1/3 from dual";
  6. //InvalidCastException: Specified cast is not valid.
  7. //var scalar = command.ExecuteScalar();
  8. var reader = command.ExecuteReader();
  9. if (reader.HasRows) {
  10. while (reader.Read()) {
  11. //InvalidCastException: Specified cast is not valid.
  12. var value = reader.GetDecimal(0);
  13. }
  14. }
  15. }

排查

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

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

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

对于一个从 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 字节序列,我们仍然需要借助其本身实现字节序列截断,实现如下:

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

后记

数据类型映射出错导致基于 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. fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer

    问题:fwrite(): send of 8192 bytes failed with errno=104 Connection reset by peer 问题描述 通过mysql + sphinx ...

  2. shell下判断文件夹或文件是否存在

    文件夹不存在则创建 if [ ! -d "/data/" ];then mkdir /data else echo "文件夹已经存在" fi 文件存在则删除 i ...

  3. 使用 shopfiy 模板语言,创建产品模板以搭配购物车实现一键购买

    shopfiy 的 product 在添加产品时,如果要将产品详情页面与购物车关联,就是在详情页里面直接下单,而不是从详情页通过点击购买按钮,跳到 shopfy stroe ,再从这个位置再跳转到下单 ...

  4. js 判断当前时间是否处于某个一个时间段内

    js 判断当前时间(或者所选时间)是否在某一时间段 我们可以使用 jutils - JavaScript常用函数库的 isDuringDate 函数来实现 传入 beginDateStr (开始时间) ...

  5. 1. Linux基本命令

    1. Linux 基本操作 1 基本命令 序号 命令 对应英文 作用 1 ls list 查看当前文件夹下的内容 2 pwd print work directory 查看当前所在文件夹 3 Cd [ ...

  6. CodeForces - 1228C(质因数分解+贡献法)

    题意 https://vjudge.net/problem/CodeForces-1228C 首先先介绍一些涉及到的定义: 定义prime(x)表示x的质因子集合.举例来说,prime(140)={2 ...

  7. UML类图基础说明

    UML类图主要由类和关系组成. 类: 什么具有相同特征的对象的抽象, 具体我也记不住, 反正有官方定义 关系: 指各个类之间的关系 类图 类就使用一个方框来表示, 把方框分成几层, 来表示不同的信息, ...

  8. 关于yyyy-MM-dd格式日期字符串,解析成LocalDateTime遇到的问题

    yyyy-MM-dd -> LocalDateTime 直接把yyyy-MM-dd格式的日期字符串解析成LocalDateTime会抛出异常 try { LocalDateTime localD ...

  9. 理解docker镜像

    镜像是用来启动容器的只读模板,是容器启动所需要的rootfs,类似于虚拟机所使用的镜像. 列出本机镜像 [root@localhost ~]# docker imagesREPOSITORY TAG ...

  10. 关于destoon6.0下的ngnix伪静态

    关于destoon6.0下的ngnix伪静态配置 ##rewrite nginx rewrite '(.*)\.(asp|aspx|asa|asax|dll|jsp|cgi|fcgi|pl)(.*)' ...