Oracle 为某个 NUMBER(4) 列返回十进制

Alv*_*uez 5 c# oracle odp.net

我正在使用 ODP.NET 执行查询。该查询从一个表中选择了很多列,包括几个 NUMBER(4) 列。

针对本地 dev 10.2 实例执行查询时,所有 NUMBER(4) 列都作为 Int16 的实例返回。这是可以的,也是意料之中的。

对另一个 11.2 实例执行查询时,除最后 NUMBER(4) 列之外的所有列也是 Int16 的实例,但最后一个是 decimal 的实例,目前正在破坏我的代码。我可以在我的应用程序中解决这个问题,但它的随意性让我很沮丧。这怎么可能?这甚至可能吗?我的意思是,这是同一个查询、同一个表、所有列的类型都相同,但其中一个在数据读取器中获得了不同的 C# 类型。这怎么会发生?

编辑 -> 我正在运行测试。即使我只从单个表中选择那一列,没有 JOIN 或 UNION 或 WHERE 或任何东西,问题也会发生。尝试删除列并重新创建它;同样的问题。尝试使用相同的列创建表的副本,问题不断发生。尝试使用较少的列创建表的副本,问题停止发生。这是否可能仅因为表中行为不端的列的列数/位置而发生?(该表有 77 列,有问题的列是表中的最后一列)。

aio*_*sov 4

是的,我们遇到了同样的问题 - ODP.NET 将输出 NUMBER 声明替换为 OracleDecimal,即使对于没有小数的值也是如此。

在我看来,如果类型被声明为 NUMBER 或像 Oracle 查询那样解释,即使值没有分数,它也会变成十进制。这与 NUMBER(N,0) 定义相反,其中 ODP.NET 实际上根据位数计算出 .NET 类型,并选择最小的有符号整数或浮点/小数类型来表示它。

如果您提供了实际的代码示例,它会有所帮助,但对于任何 ExecuteDataReader() 调用,以下代码应该是安全的:

  using(var reader = cmd.ExecuteReader())
  {
    while(reader.Read())
    {
      var v = Convert.ToInt16(reader["name"]);
    }
  }
Run Code Online (Sandbox Code Playgroud)

输出参数的问题会变得更糟,因为 OracleDecimal 没有实现 IConvertible,并且不会使用 Convert.ToInt32() 调用转换为 Int32 或类似类型(它会失败并出现异常)。

问题是 OracleDataReader.GetInt16() 的实现如下:

  int num2 = (int) this.m_pOpoMetValCtx->pColMetaVal[i].Scale;
  int num3 = (int) this.m_pOpoMetValCtx->pColMetaVal[i].Precision;
  if (num2 > 0 || num3 - num2 >= 5)
    throw new InvalidCastException();
Run Code Online (Sandbox Code Playgroud)

所以它可能仍然会失败,但至少你会知道,由于某种未知的原因,该规模是积极的。这种模式在多个地方重复,一般来说,所有 NUMBER 类型都由 ODP.NET 在内部表示为 OracleDecimal,然后根据比例和精度映射到 Int16、Int32、Int64、Float、Double 和 Decimal

所以对于下面的查询

  SELECT CAST(1 AS NUMBER(3,0)) as c1, CAST(220 AS NUMBER) as c2 FROM DUAL
Run Code Online (Sandbox Code Playgroud)

以下作品:

  var v = Convert.ToInt16(reader["c2"]);
Run Code Online (Sandbox Code Playgroud)

而以下失败:

  var v = reader.GetInt16(1); // InvalidCastException
Run Code Online (Sandbox Code Playgroud)

我发现的另一个解决方法是在定义输出或返回参数时,使用 OracleDbTypeEx 会有所帮助。

例如,如果 oracle 输出参数创建为

  var p = new OracleParameter("name",OracleDbType.Int32, ParameterDirection.Output)
Run Code Online (Sandbox Code Playgroud)

它以 OracleDecimal 形式返回并导致前面描述的问题,但是当这样声明时:

  var p = new OracleParameter("name",OracleDbType.Int32, ParameterDirection.Output);
  p.OracleDbCodeEx = OracleDbType.Int32;
Run Code Online (Sandbox Code Playgroud)

它工作正常,值类型实际上是 .net 类型,并且是 System.Int32。

总体而言,ODP.NET 的实现相当糟糕,包括一些方法重载的选择。对于另一个示例,OracleDataReader.GetOrdinal() 被实现为对列名称的循环。该库的用户必须非常小心并特别注意功能测试。