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



我有一个遗留数据库,它是用数据库类型INTEGER为许多(1000 +)Oracle列创建的。MS SQL中存在一个具有相同结构的数据库。正如我被告知的那样,最初的定义是使用一个工具创建的,该工具从一个逻辑模型生成脚本,以用于MS SQL和Oracle的特定模型。

使用c++和MFC,列被很好地映射为两个DBMs的整数类型。

我正在将这个应用程序移植到。net和c#。使用相同的c#代码库来访问MS SQL和Oracle。我们使用相同的数据集和逻辑,我们需要相同的类型(int32)。

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

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

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

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

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]

then truncate原表

truncate [YOUR_TABLE_NAME]

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

insert /*+ append */ into [YOUR_TABLE_NAME] select * from temp_table
commit

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

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

  1. 一般来说,我通常更喜欢在数据库中设置适当的数据类型,所以如果可能的话,我会更改列数据类型,因为它们更好地表示实际值及其约束。

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

然后你也有一些应用程序实现选项:

  1. 实现您自己的数据阅读器或ADO的子集。. NET类(从DbProviderFactory, DbConnection, dbcommand, DbDataReader等继承,并包装Oracle类),这取决于你的实现有多复杂。Oracle。DataAccess, Devart和所有的提供商都做了完全相同的事情,因为它提供了对一切的完全控制,包括对数据类型的任何魔法。如果数据类型转换是您唯一想要实现的,那么大部分实现将只是调用包装的类方法/属性。

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

然而,这将不能与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;
    }
}

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

编辑:增加GetFieldType方法的hack。但是,静态映射哈希表可能会更新,因此可能会产生不必要的影响。你需要进行适当的测试。fieldTypes数组保存数据阅读器的所有列返回的类型。

最新更新