65.9K
CodeProject 正在变化。 阅读更多。
Home

DBTool – 第二部分:Harlinn.Oracle

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (20投票s)

2013年7月30日

CPOL

10分钟阅读

viewsIcon

44124

downloadIcon

1287

一个基于反射的 .NET 客户端 Oracle 数据提供程序的包装器 – 允许您的应用程序动态加载客户端计算机上安装的 Oracle.DataAccess.dll 程序集。

引言

DBTool 允许您浏览 Oracle 数据库的内容,并生成 C# 代码,您可以在自己的代码中使用这些代码来访问数据库。在这篇关于 DBTool 的第二篇文章中,我们将介绍我正在为 DBTool 开发的 Harlinn.Oracle.dll 程序集。

使用 Oracle 的 Oracle.DataAccess.dll 程序集的一个令人烦恼之处在于,您通常需要显式引用它才能访问未通过通用基类公开的功能。另一件事是,到目前为止,Oracle.DataAccess.dll 要么是显式的 32 位,要么是显式的 64 位,这意味着您必须在编译时决定是创建 32 位还是 64 位应用程序。

Harlinn.Oracle.dll 是一个使用反射来访问许多需要显式程序集引用的功能的程序集。通过缓存属性、方法、构造函数和事件的信息,反射的使用开销被降至最低 – 实际上,我对它的性能表现感到非常惊讶。

Created user HarlinnOracle
Created sequence TEST_SEQ 
Created table TEST_TABLE 
ODPConnection: Inserted and retrieved 50000 records in 13,4124415 seconds
OracleConnection: Inserted and retrieved 50000 records in 21,7103289 seconds
ODPConnection: Inserted and retrieved 50000 records in 13,2898706 seconds
OracleConnection: Inserted and retrieved 50000 records in 12,9866745 seconds
ODPConnection: Inserted and retrieved 50000 records in 19,1152156 seconds
OracleConnection: Inserted and retrieved 50000 records in 20,1206863 seconds
ODPConnection: Inserted and retrieved 50000 records in 13,2173342 seconds
OracleConnection: Inserted and retrieved 50000 records in 24,8340471 seconds
ODPConnection: Inserted and retrieved 50000 records in 13,2524953 seconds
OracleConnection: Inserted and retrieved 50000 records in 12,7241699 seconds
ODPConnection: Inserted and retrieved 50000 records in 13,2143947 seconds
OracleConnection: Inserted and retrieved 50000 records in 14,1340197 seconds
ODPConnection: Inserted and retrieved 50000 records in 37,0409351 seconds
OracleConnection: Inserted and retrieved 50000 records in 13,1895755 seconds
ODPConnection: Inserted and retrieved 50000 records in 13,6237786 seconds
OracleConnection: Inserted and retrieved 50000 records in 34,1442815 seconds
ODPConnection: Inserted and retrieved 50000 records in 26,0132111 seconds
OracleConnection: Inserted and retrieved 50000 records in 13,1367761 seconds
ODPConnection: Inserted and retrieved 50000 records in 19,246613 seconds
OracleConnection: Inserted and retrieved 50000 records in 17,0021234 seconds
ODPConnection execution time: 181,4262897 seconds
OracleConnection execution time: 183,9826829 seconds
Dropped user HarlinnOracle

执行时间几乎相同。

生成上述结果的核心代码如下所示:

for (int i = 0; i < count; i++)
{
    using (ODPCommand cmd = con.CreateCommand())
    {
                    
        string sql = "INSERT INTO " + Constants.User + 
                     ".TEST_TABLE(ID,TEXT) VALUES(:id,:text)";
        cmd.CommandText = sql;
        cmd.Parameters.Add(":id", i + 1);
        cmd.Parameters.Add(":text", "ROW "+ i + 1);
        cmd.ExecuteNonQuery();
                        
    }
}

using (ODPCommand cmd = con.CreateCommand())
{
    string sql = "SELECT ID,TEXT FROM " + Constants.User + ".TEST_TABLE";
    cmd.CommandText = sql;
    using (ODPDataReader reader = cmd.ExecuteReader())
    {
        long sum = 0;
        while (reader.Read())
        {
            sum += reader.GetOracleDecimal(0).ToInt32();
        }
    }
}

测试 OracleConnectionOracleCommandOracleDataReader 的代码是相同的,只是它直接使用了 Oracle 的 ODP.Net 类。虽然计时差异很大,但我认为可以公平地说,这表明使用我的基于反射的类似乎对性能影响不大。

该测试经常执行基于反射的代码。GetOracleDecimal 返回一个 ODPDecimal,它完全基于反射,而 cmd.Parameters.Add 方法也使用反射实现。

DBTool 现在使用“Oracle.DataAccess.Client”提供程序名称加载 Oracle.DataAccess.dll。这意味着 DBTool 现在将使用 machine.config 文件中注册的 Oracle.DataAccess.dll,并且相同的可执行文件可用于 32 位和 64 位执行。

该库现在足够完整,可以满足 DBTool 的需求,并且主要类是:

  • ODPConnection
  • ODPCommand
  • ODPParameterCollection
  • ODPParameter
  • ODPDataReader
  • ODPTransaction

还有许多类用于处理 Oracle 特定的列类型:

  • ODPBFile
  • ODPBinary
  • ODPBlob
  • ODPClob
  • ODPDate
  • ODPDecimal
  • ODPIntervalDS
  • ODPIntervalYM
  • ODPRef
  • ODPString
  • ODPTimeStamp
  • ODPTimeStampLTZ
  • ODPTimeStampTZ
  • ODPXmlStream
  • ODPXmlType

为什么?

Oracle RDBMS 可能是用于存储工业管理系统数据最常用的数据库系统。多年来,我发现显式引用 Oracle.DataAccess.dll 会给系统管理员带来一些麻烦,因为他们通常希望能够升级 Oracle 客户端安装,而无需重新编译使用 Oracle.DataAccess.dll 程序集的应用程序。这是为了解决这个问题。

Harlinn.Oracle.dll 目前仍在开发中,我希望能就我正在实现的功能的有用性获得一些反馈。

简要介绍反射

反射是指 .NET 运行时提供的功能,它允许我们在不知道其标识或正式结构的情况下检查和操作代码实体。

在 .NET 中,反射允许我们分析对象和类型,并收集有关其定义和行为的信息。这些信息可用于动态创建新对象和动态调用方法。

大多数 .NET 中的反射相关功能由 System.Reflection 命名空间中的类提供。我们还有 System.Type 类,它提供了与反射相关的许多重要功能。

最常用的类是:

  • System.Type:

    在 .NET 中,一切都有一个 Type,它是访问有关对象、结构和基本数据类型的元数据的主要机制。Type 允许我们检索有关类的构造函数、方法、字段、属性和事件的信息,以及包含实现的模块和程序集。

  • System.Reflection.Assembly:

    一个 .NET 应用程序是程序集的集合,通常是 *.exe 或 *.dll 文件。许多 .NET 的功能是通过作为 .NET 运行时一部分提供的程序集来实现的。程序集包含(除其他外)实现我们应用程序使用的类和结构的字节码。Assembly 类表示单个程序集,并提供允许我们访问程序集中实现的类型的功能。

  • System.Reflection.ConstructorInfo:

    我们可以使用 Type.GetConstructor 方法检索有关类型的构造函数的信息。ConstructorInfo 还允许我们通过 Invoke 方法创建 Type 的实例。

  • System.Reflection.EventInfo:

    我们可以使用 Type.GetEvent 方法检索有关类型的事件的信息。EventInfo 还允许我们为类型的实例添加和删除事件处理程序。

  • System.Reflection.FieldInfo:

    我们可以使用 Type.GetField 方法检索有关类型的字段的信息。FieldInfo 还允许我们为类型的实例设置和检索字段的值。

  • System.Reflection.MethodInfo:

    我们可以使用 Type.GetMethod 方法检索有关类型实现的方法的信息。MethodInfo 还允许我们通过 Invoke 方法调用这些方法。

  • System.Reflection.PropertyInfo:

    我们可以使用 Type.GetProperty 方法检索有关类型的属性的信息。PropertyInfo 还允许我们为类型的实例设置和检索属性的值。

MSDN 杂志中的以下文章提供了对反射相关的一些可能性和陷阱的见解:

用法

如果您知道如何使用 Oracle.DataAccess.dll 程序集,那么您会发现该库非常简单易用。

public static ColumnReader CreateReader(ODPConnection oracleConnection, 
                                        string owner, string tableName, string columnName)
{
    try
    {
        string fullSelect = string.Format(FULL_SELECT, DEFAULT_QUALIFIED_DBNAME);
        ODPCommand oracleCommand = oracleConnection.CreateCommand();
        using (oracleCommand)
        {
            oracleCommand.BindByName = true;
            string queryFilter = " WHERE OWNER = :owner AND "+
                                 " TABLE_NAME = :tableName AND COLUMN_NAME = :columnName";
            string selectStatement = fullSelect + queryFilter;
            oracleCommand.CommandText = selectStatement;


            var ownerParameter = 
                oracleCommand.Parameters.Add(new ODPParameter(":owner", ODPDbType.Varchar2));
            ownerParameter.Value = owner;

            var tableNameParameter = 
                oracleCommand.Parameters.Add(new ODPParameter(":tableName", ODPDbType.Varchar2));
            tableNameParameter.Value = tableName;

            var columnNameParameter = 
                oracleCommand.Parameters.Add(new ODPParameter(":columnName", ODPDbType.Varchar2));
            columnNameParameter.Value = columnName;

            var result = 
                oracleCommand.RawExecuteReader(CommandBehavior.SingleResult | 
                                               CommandBehavior.SingleRow);
            return new ColumnReader(result);
        }
    }
    catch (Exception exc)
    {
        LogException(exc, MethodBase.GetCurrentMethod());
        throw;
    }
}

ODPConnection

ODPConnection 实现 System.Data.IDbConnection 接口,并通过反射公开以下功能:

属性

ActionName允许应用程序为给定的 OracleConnection 对象设置应用程序上下文中的操作名称。
ClientId允许应用程序为给定的 OracleConnection 对象设置应用程序上下文中的客户端标识符。
ClientInfo允许应用程序为给定的 OracleConnection 对象设置应用程序上下文中的客户端信息。
DatabaseDomainName检索此连接所连接的数据库域。
DatabaseName检索此连接所连接的数据库名称。
HostName检索此连接所连接的主机名称。
InstanceName检索此连接所连接的实例名称。
ModuleName允许应用程序为给定的 OracleConnection 对象设置应用程序上下文中的模块名称。
ServiceName检索此连接所连接的服务名称。
StatementCacheSize检索与此连接关联的语句缓存的当前大小。

方法

ClearAllPools清除所有连接池中的所有连接。
ClearPool清除与 OracleConnection 对象关联的连接池。
FlushCache刷新通过此连接检索的 REF 对象进行的所有更新和删除。
OpenWithNewPassword使用作为参数传递的新密码打开新连接。旧密码必须使用 Password 属性在连接字符串中提供。

ODPCommand

ODPCommand 实现 System.Data.IDbCommand 接口,并通过反射公开以下功能:

属性

  • BindByName:指定参数集合的绑定方法。如果参数按名称绑定,则设置为 true;如果参数按位置绑定,则设置为 false。

ODPParameterCollection

ODPParameterCollection 实现 System.Data.IDataParameterCollection 接口,并通过反射公开以下功能:方法:

  • ODPParameter Add(ODPParameter param):将提供的 ODEParameter 对象添加到参数集合中。
  • ODPParameter Add(string name, object value):创建一个新的 ODEParameter 并将其添加到参数集合中。
  • ODPParameter Add(string name, ODPDbType dbType):创建一个新的 ODEParameter 并将其添加到参数集合中。
  • ODPParameter Add(string name, ODPDbType dbType, ParameterDirection direction):创建一个新的 ODEParameter 并将其添加到参数集合中。
  • ODPParameter Add(string name, ODPDbType dbType, int size, object val, ParameterDirection direction):创建一个新的 ODEParameter 并将其添加到参数集合中。
  • ODPParameter Add(string name, ODPDbType dbType, int size):创建一个新的 ODEParameter 并将其添加到参数集合中。
  • ODPParameter Add(string name, ODPDbType dbType, int size, string sourceColumn):创建一个新的 ODEParameter 并将其添加到参数集合中。

ODPParameter

ODPParameter 实现 System.Data.IDbDataParameter 接口,并通过反射公开以下功能:

构造函数

  • ODPParameter(string name, ODPDbType dbType)

属性

  • ArrayBindSize:指定 Array Bind 或 PL/SQL Associative Array Bind 执行之前或之后的参数的输入或输出大小。
  • ArrayBindStatus:指定 Array Bind 或 PL/SQL Associative Array Bind 执行之前或之后的参数的输入或输出状态。
  • CollectionType:指定 OracleParameter 是否表示集合,如果是,则指定集合类型。
  • Offset:指定 Value 属性的偏移量,或 Value 属性中元素的偏移量。
  • ODPDbType:指定数据类型。
  • Status:指定与 Value 属性中的数据相关的执行状态。

ODPDataReader

ODPDataReader 实现 System.Data.IDataReader 接口,并通过反射公开以下功能:

方法

  • byte[] GetBytes(int i):使用 GetOracleBinary 检索数据。
  • long GetInt64(int i):使用 GetOracleDecimal 检索数据。
  • TimeSpan GetTimeSpan(int i):此方法返回指定 INTERVAL DAY TO SECOND 列的 TimeSpan 值。
  • ODPBinary GetOracleBinary(int i):此方法返回指定列的 ODPBinary 对象。
  • ODPDecimal GetOracleDecimal(int i):此方法返回指定 NUMBER 列的 ODPDecimal 对象。
  • ODPBFile GetOracleBFile(int i):此方法返回指定 BFILE 列的 ODPBFile 对象。
  • ODPBlob GetOracleBlob(int index):此方法返回指定 BLOB 列的 ODPBlob 对象。
  • ODPClob GetOracleClob(int index):此方法返回指定 CLOB 列的 ODPClob 对象。
  • ODPDate GetOracleDate(int index):此方法返回指定 DATE 列的 ODPDate 对象。
  • ODPIntervalDS GetOracleIntervalDS(int index):此方法返回指定 INTERVAL DAY TO SECOND 列的 ODPIntervalDS 对象。
  • ODPIntervalYM GetOracleIntervalYM(int index):此方法返回指定 INTERVAL YEAR TO MONTH 列的 ODPIntervalYM 对象。
  • ODPRef GetOracleRef(int index):此方法返回指定 REF 列的 ODPRef 对象。
  • ODPString GetOracleString(int index):此方法返回指定列的 ODPString 对象。
  • ODPTimeStamp GetOracleTimeStamp(int index):此方法返回指定 TimeStamp 列的 ODPTimeStamp 对象。
  • ODPTimeStampLTZ GetOracleTimeStampLTZ(int index):此方法返回指定 TimeStamp WITH LOCAL TIME ZONE 列的 ODPTimeStampLTZ 对象。
  • ODPTimeStampTZ GetOracleTimeStampTZ(int index):此方法返回指定 TimeStamp WITH TIME ZONE 列的 ODPTimeStampTZ 对象。
  • object GetOracleValue(int index):此方法将指定的列值作为 ODPxxx 类型返回。
  • int GetOracleValues(object[] values):此方法将所有列值作为 ODPxxx 类型获取。
  • ODPXmlType GetOracleXmlType(int index):此方法返回指定 XMLType 列的 ODPXmlType 对象。
  • XmlReader GetXmlReader(int index):此方法将 XMLType 列的内容作为 .NET XmlReader 对象的实例返回。

ODPTransaction

ODPTransaction 实现 System.Data.IDbTransaction 接口,并通过反射公开以下功能:

方法

  • void Rollback(string savepointName):此方法将数据库事务回滚到当前事务中的一个保存点。
  • void Save(string savepointName):此方法在当前事务中创建一个保存点。savepointName 区分大小写,保存点是一种机制,允许部分事务回滚,而不是整个事务。

内部

ODPAssemblyHelper 类负责缓存类型信息和定位 Oracle.DataAccess.dll 程序集。

GetAssemblyFromProviderFactory 方法尝试使用提供程序名称访问 Oracle.DataAccess.dll 程序集。

Assembly GetAssemblyFromProviderFactory()
{
    try
    {
        DbProviderFactory factory = 
               DbProviderFactories.GetFactory("Oracle.DataAccess.Client");
        if (factory != null)
        {
            return factory.GetType().Assembly;
        }
    }
    catch (Exception exc)
    {
        LogException(exc, System.Reflection.MethodBase.GetCurrentMethod());
    }
    return null;
}

如果 GetAssemblyFromProviderFactory 失败,该库将尝试使用已弃用的 Assembly.LoadWithPartialName 方法加载程序集。

Assembly Assembly
{
    get
    {
        if (assembly == null)
        {
            assembly = GetAssemblyFromProviderFactory();
            if (assembly == null)
            {
                assembly = Assembly.LoadWithPartialName("Oracle.DataAccess");
            }
        }
        return assembly;
    }
}

为了避免重复调用 Type.GetPropertyType.GetMethodType.GetEventType.GetConstructor,该库使用一组辅助类来检索和缓存与 Oracle.DataAccess.dll 程序集中实现的类进行交互所需的 PropertyInfoMethodInfoEventInfoConstructorInfo 对象。

ODPConnectionHelper()
{
    try
    {
        actionName = Type.GetProperty("ActionName");
        clientId = Type.GetProperty("ClientId");
        clientInfo = Type.GetProperty("ClientInfo");
        databaseDomainName = Type.GetProperty("DatabaseDomainName");
        databaseName = Type.GetProperty("DatabaseName");
        hostName = Type.GetProperty("HostName");
        instanceName = Type.GetProperty("InstanceName");
        moduleName = Type.GetProperty("ModuleName");
        serviceName = Type.GetProperty("ServiceName");
        statementCacheSize = Type.GetProperty("StatementCacheSize");

        failover = Type.GetEvent("Failover");

        clearAllPools = Type.GetMethod("ClearAllPools", BindingFlags.Static);
        clearPool = Type.GetMethod("ClearPool", BindingFlags.Static);
        flushCache = Type.GetMethod("FlushCache");
        openWithNewPassword = Type.GetMethod("OpenWithNewPassword", new Type[] { typeof(string) });
    }
    catch (Exception exc)
    {
        LogException(exc, System.Reflection.MethodBase.GetCurrentMethod());
        throw;
    }
}

辅助类是单例对象,通过静态 Instance 属性访问。

public static ODPConnectionHelper Instance
{
    get
    {
        try
        {
            if (instance == null)
            {
                lock (synchObject)
                {
                    if (instance == null)
                    {
                        instance = new ODPConnectionHelper();
                    }
                }
            }
            return instance;
        }
        catch (Exception exc)
        {
            LogException(exc, System.Reflection.MethodBase.GetCurrentMethod());
            throw;
        }
    }
}

历史

  • 2013 年 7 月 30 日 - 首次发布。
  • 2013 年 8 月 1 日
    • ODPTransaction 添加了对保存点的支持。
    • ODPDataReader 添加了对 ODPBinaryODPBFileODPDecimal 的支持。
    • ODPDataReader 添加了对 ODPBlobODPClob 的初步支持。
    • 一些错误修复。
  • 2013 年 8 月 6 日
    • ODPTimeStamp:实现了构造函数和属性。
    • ODPTimeStampLTZ:实现了构造函数和属性。
    • ODPTimeStampTZ:实现了构造函数和属性。
  • 2013 年 10 月 1 日:进行了一些修复和小型增强。
© . All rights reserved.