我可以控制Oracle如何映射ADO.NET中的整数类型?

时间:2015-09-16 09:16:44

标签: oracle ado.net

我有一个使用数据库类型INTEGER为许多(1.000+)Oracle列创建的旧数据库。 MS SQL存在具有相同结构的数据库。据我所知,原始定义是使用一个工具创建的,该工具生成从逻辑模型到MS SQL和Oracle的特定脚本的脚本。

使用C ++和MFC将列很好地映射到两个DBM的整数类型。

我将此应用程序移植到.NET和C#。相同的C#代码库用于访问MS SQL和Oracle。我们使用相同的DataSet和逻辑,我们需要相同的类型(两者都是int32)。

Oracle的ODP.NET驱动程序将它们映射到Decimal。这是合乎逻辑的,因为Oracle自动将整数列创建为NUMBER(37)。 MS SQL中的列映射到int32。

我能以某种方式控制如何映射ODP.NET驱动程序中的类型吗?我想说"将NUMBER(37)映射到int32"。列永远不会保存大于int32限制的值。我们知道这一点,因为它正在MS SQL版本中使用。

或者,我可以修改NUMBER(37)到NUMBER(8)或SIMPLE_INTEGER中的所有列,以便它们为我们映射到正确的类型吗?其中许多列都用作主键(想想自动增量)。

2 个答案:

答案 0 :(得分:1)

关于类型映射,希望这是你需要的 http://docs.oracle.com/cd/E51173_01/win.122/e17732/entityDataTypeMapping.htm#ODPNT8300
关于类型更改,如果table为空,您可以使用以下脚本(只需将[YOUR_TABLE_NAME]替换为大写的表名):

notify

如果其中一些列不为空,则无法降低它们的精度 如果您没有太多数据,可以将其移至临时表

DECLARE
  v_table_name CONSTANT VARCHAR2(30) := '[YOUR_TABLE_NAME]';
BEGIN
  FOR col IN (SELECT * FROM user_tab_columns WHERE table_name = v_table_name AND data_type = 'NUMBER' AND data_length = 37)
  LOOP
    EXECUTE IMMEDIATE 'ALTER TABLE '||v_table_name||' MODIFY '||col.column_name||' NUMBER(8)';
  END LOOP;
END;

然后截断原始表

create table temp_table as select * from [YOUR_TABLE_NAME]

然后在上面运行脚本
然后移回数据

truncate [YOUR_TABLE_NAME]

如果数据量很大,最好将其移动一次。在这种情况下,创建具有正确数据类型和所有索引,约束等的新表更快,然后移动数据,然后重命名两个表以使新表具有正确的名称。

答案 1 :(得分:1)

不幸的是,.NET和Oracle之间的数字类型映射在OracleDataReader类中是硬编码的。

  1. 一般情况下,我通常更喜欢在数据库中设置适当的数据类型,因此如果可能的话,我会更改列数据类型,因为它们更能代表实际值及其约束。

  2. 另一种选择是使用转换为NUMBER(8)的视图来封装表,但会对执行计划产生负面影响,因为它禁止索引查找。

  3. 然后你还有一些应用程序实现选项:

    1. 实现您自己的数据读取器或ADO.NET类的子集(继承自DbProviderFactory,DbConnection,DbCommmand,DbDataReader等,并包装Oracle类),具体取决于您的实现的复杂程度。 Oracle.DataAccess,Devart和所有提供程序完全相同,因为它可以完全控制所有内容,包括数据类型的任何魔法。如果数据类型转换是您想要实现的唯一事情,那么大多数实现只是调用包装类方法/属性。

    2. 如果在执行命令之后和开始阅读之前有权访问OracleDataReader,则可以执行简单的hack并使用反射设置结果数字类型(以下实现只是简化演示)。

    3. 但是这不适用于ExecuteScalar,因为此方法永远不会暴露底层数据读取器。

      var connection = new OracleConnection("DATA SOURCE=HQ_PDB_TCP;PASSWORD=oracle;USER ID=HUSQVIK");
      connection.Open();
      
      var command = connection.CreateCommand();
      command.CommandText = "SELECT 1 FROM DUAL";
      
      var reader = command.ExecuteDatabaseReader();
      reader.Read();
      Console.WriteLine(reader[0].GetType().FullName);
      Console.WriteLine(reader.GetFieldType(0).FullName);
      
      public static class DataReaderExtensions
      {
          private static readonly FieldInfo NumericAccessorField = typeof(OracleDataReader).GetField("m_dotNetNumericAccessor", BindingFlags.NonPublic | BindingFlags.Instance);
          private static readonly object Int32DotNetNumericAccessor = Enum.Parse(typeof(OracleDataReader).Assembly.GetType("Oracle.DataAccess.Client.DotNetNumericAccessor"), "GetInt32");
          private static readonly FieldInfo MetadataField = typeof(OracleDataReader).GetField("m_metaData", BindingFlags.NonPublic | BindingFlags.Instance);
          private static readonly FieldInfo FieldTypesField = typeof(OracleDataReader).Assembly.GetType("Oracle.DataAccess.Client.MetaData").GetField("m_fieldTypes", BindingFlags.NonPublic | BindingFlags.Instance);
      
          public static OracleDataReader ExecuteDatabaseReader(this OracleCommand command)
          {
              var reader = command.ExecuteReader();
              var columnNumericAccessors = (IList)NumericAccessorField.GetValue(reader);
              columnNumericAccessors[0] = Int32DotNetNumericAccessor;
      
              var metadata = MetadataField.GetValue(reader);
              var fieldTypes = (Type[])FieldTypesField.GetValue(metadata);
              fieldTypes[0] = typeof(Int32);
      
              return reader;
          }
      }
      

      我实现了命令执行的扩展方法,返回读者,我可以设置所需的列数字类型。如果不设置数字访问器(它只是内部枚举Oracle.DataAccess.Client.DotNetNumericAccessor),您将获得System.Decimal,使用访问器设置获得Int32。使用它可以获得所有Int16,Int32,Int64,Float或Double。 columnNumericAccessors index是一个列索引,它只应用于数值类型,如果列为DATE或VARCHAR,则只忽略数字访问器。如果您的实现没有公开提供者特定类型,请在IDbCommand或DbCommand上创建扩展方法,然后将DbDataReader安全地转换为OracleDataReader。

      编辑:为GetFieldType方法添加了hack。但是可能会发生静态映射哈希表可能会更新,因此可能会产生不必要的影响。你需要正确测试它。 fieldTypes数组包含为数据读取器的所有列返回的类型。