我有一个使用数据库类型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中的所有列,以便它们为我们映射到正确的类型吗?其中许多列都用作主键(想想自动增量)。
答案 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类中是硬编码的。
一般情况下,我通常更喜欢在数据库中设置适当的数据类型,因此如果可能的话,我会更改列数据类型,因为它们更能代表实际值及其约束。
另一种选择是使用转换为NUMBER(8)的视图来封装表,但会对执行计划产生负面影响,因为它禁止索引查找。
然后你还有一些应用程序实现选项:
实现您自己的数据读取器或ADO.NET类的子集(继承自DbProviderFactory,DbConnection,DbCommmand,DbDataReader等,并包装Oracle类),具体取决于您的实现的复杂程度。 Oracle.DataAccess,Devart和所有提供程序完全相同,因为它可以完全控制所有内容,包括数据类型的任何魔法。如果数据类型转换是您想要实现的唯一事情,那么大多数实现只是调用包装类方法/属性。
如果在执行命令之后和开始阅读之前有权访问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;
}
}
我实现了命令执行的扩展方法,返回读者,我可以设置所需的列数字类型。如果不设置数字访问器(它只是内部枚举Oracle.DataAccess.Client.DotNetNumericAccessor),您将获得System.Decimal,使用访问器设置获得Int32。使用它可以获得所有Int16,Int32,Int64,Float或Double。 columnNumericAccessors index是一个列索引,它只应用于数值类型,如果列为DATE或VARCHAR,则只忽略数字访问器。如果您的实现没有公开提供者特定类型,请在IDbCommand或DbCommand上创建扩展方法,然后将DbDataReader安全地转换为OracleDataReader。
编辑:为GetFieldType方法添加了hack。但是可能会发生静态映射哈希表可能会更新,因此可能会产生不必要的影响。你需要正确测试它。 fieldTypes数组包含为数据读取器的所有列返回的类型。