DBTool – 第二部分:Harlinn.Oracle






4.97/5 (20投票s)
一个基于反射的 .NET 客户端 Oracle 数据提供程序的包装器 – 允许您的应用程序动态加载客户端计算机上安装的 Oracle.DataAccess.dll 程序集。
引言
- 关于 DBTool 的第一篇文章可以在这里找到: DBTool for Oracle - Part 1[^]
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(); } } }
测试 OracleConnection
、OracleCommand
和 OracleDataReader
的代码是相同的,只是它直接使用了 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
列的内容作为 .NETXmlReader
对象的实例返回。
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.GetProperty
、Type.GetMethod
、Type.GetEvent
和 Type.GetConstructor
,该库使用一组辅助类来检索和缓存与 Oracle.DataAccess.dll 程序集中实现的类进行交互所需的 PropertyInfo
、MethodInfo
、EventInfo
和 ConstructorInfo
对象。
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
添加了对ODPBinary
、ODPBFile
和ODPDecimal
的支持。 - 为
ODPDataReader
添加了对ODPBlob
和ODPClob
的初步支持。 - 一些错误修复。
- 为
- 2013 年 8 月 6 日
-
ODPTimeStamp
:实现了构造函数和属性。 -
ODPTimeStampLTZ
:实现了构造函数和属性。 -
ODPTimeStampTZ
:实现了构造函数和属性。
-
- 2013 年 10 月 1 日:进行了一些修复和小型增强。