ODP.NET 托管库会解析别名,但 32 位库会解析
ODP.NET Managed library does resolve alias, but 32-bit library does
我的机器上安装了 32 位驱动程序(它们是由一些 DBA 安装和配置的)
我写了一个简单的脚本来测试驱动程序,大致如下
using (DataTable table = new DataTable())
{
using (OracleConnection connection = new OracleConnection())
{
connection.ConnectionString = "Data Source=alias;User id=user;Password=password";
connection.Open();
using (OracleCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "SELECT * FROM DUAL";
table.Load(command.ExecuteReader());
}
}
}
当我使用 32 位 Oracle.DataAccess.dll 将此应用程序编译为 32 位时,它可以顺利执行。
但是,如果我使用 Oracle.ManagedDataAccess.dll 将应用程序编译为 AnyCPU,我会收到 ORA-12154(无法解析指定的连接标识符)错误。
如果我 tnsping 别名,它会正常工作并告诉我连接标识符和真实的数据库名称。
如果我随后更改连接字符串以使用真实数据库名称而不是别名,然后再次尝试使用托管库,它会顺利执行。
我一直在阅读并找到 this 答案,其中说托管驱动程序依赖于 tnsnames.ora 文件来解析别名,但是我依赖于 [=34= 中定义的 LDAP 服务器] 和 ldap.ora.
当我 tnsping 时,它说它使用 sqlnet.ora 来解析名称。
那么托管驱动程序为何不起作用?
ODP.NET 托管驱动程序依赖于配置文件中设置的 TNS_ADMIN 值来查找 TNSNAMES.ORA 文件。如果不想设置该值,可以在可执行文件的工作目录中包含 TNSNAMES.ORA 文件,或者直接在配置文件中创建 TNS 别名。
编辑:如果您依赖 SQLNET.ORA 和 LDAP.ORA,您也可以将它们复制到工作目录中,或者在您的配置文件中设置 LDAP 参数。有关可用于 LDAP 的配置文件参数,请参阅 ODP.NET 文档。
希望这个解决方法适合您。您可以自己查询LDAP服务器,并将完整的连接字符串放入您的代码中。
您可以使用以下代码解析来自 LDAP 的连接字符串:
using (OracleConnection connection = new OracleConnection())
{
connection.ConnectionString = "Data Source=" + ResolveServiceNameLdap("alias") + ";User id=user;Password=password";
connection.Open();
}
...
private static string ResolveServiceNameLdap(string serviceName)
{
string tnsAdminPath = Path.Combine(@"C:\oracle\network", "ldap.ora");
// or something more advanced...
// ldap.ora can contain many LDAP servers
IEnumerable<string> directoryServers = null;
if ( File.Exists(tnsAdminPath) ) {
string defaultAdminContext = string.Empty;
using ( var sr = File.OpenText(tnsAdminPath) ) {
string line;
while ( ( line = sr.ReadLine() ) != null ) {
// Ignore comments or empty lines
if ( line.StartsWith("#") || line == string.Empty )
continue;
// If line starts with DEFAULT_ADMIN_CONTEXT then get its value
if ( line.StartsWith("DEFAULT_ADMIN_CONTEXT") )
defaultAdminContext = line.Substring(line.IndexOf('=') + 1).Trim(new[] { '\"', ' ' });
// If line starts with DIRECTORY_SERVERS then get its value
if ( line.StartsWith("DIRECTORY_SERVERS") ) {
string[] serversPorts = line.Substring(line.IndexOf('=') + 1).Trim(new[] { '(', ')', ' ' }).Split(',');
directoryServers = serversPorts.SelectMany(x => {
// If the server includes multiple port numbers, this needs to be handled
string[] serverPorts = x.Split(':');
if ( serverPorts.Count() > 1 )
return serverPorts.Skip(1).Select(y => string.Format("{0}:{1}", serverPorts.First(), y));
return new[] { x };
});
}
}
}
// Iterate through each LDAP server, and try to connect
foreach ( string directoryServer in directoryServers ) {
// Try to connect to LDAP server with using default admin contact
try {
var directoryEntry = new DirectoryEntry("LDAP://" + directoryServer + "/" + defaultAdminContext, null, null, AuthenticationTypes.Anonymous);
var directorySearcher = new DirectorySearcher(directoryEntry, "(&(objectclass=orclNetService)(cn=" + serviceName + "))", new[] { "orclnetdescstring" }, SearchScope.Subtree);
SearchResult searchResult = directorySearcher.FindOne();
var value = searchResult.Properties["orclnetdescstring"][0] as byte[];
if ( value != null )
connectionString = Encoding.Default.GetString(value);
// If the connection was successful, then not necessary to try other LDAP servers
break;
} catch {
// If the connection to LDAP server not successful, try to connect to the next LDAP server
continue;
}
}
// If casting was not successful, or not found any TNS value, then result is an error
if ( string.IsNullOrEmpty(connectionString) )
throw new Exception("TNS value not found in LDAP");
} else {
// If ldap.ora doesn't exist, then throw error
throw new FileNotFoundException("ldap.ora not found");
}
return connectionString;
}
我的机器上安装了 32 位驱动程序(它们是由一些 DBA 安装和配置的)
我写了一个简单的脚本来测试驱动程序,大致如下
using (DataTable table = new DataTable())
{
using (OracleConnection connection = new OracleConnection())
{
connection.ConnectionString = "Data Source=alias;User id=user;Password=password";
connection.Open();
using (OracleCommand command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = "SELECT * FROM DUAL";
table.Load(command.ExecuteReader());
}
}
}
当我使用 32 位 Oracle.DataAccess.dll 将此应用程序编译为 32 位时,它可以顺利执行。
但是,如果我使用 Oracle.ManagedDataAccess.dll 将应用程序编译为 AnyCPU,我会收到 ORA-12154(无法解析指定的连接标识符)错误。
如果我 tnsping 别名,它会正常工作并告诉我连接标识符和真实的数据库名称。
如果我随后更改连接字符串以使用真实数据库名称而不是别名,然后再次尝试使用托管库,它会顺利执行。
我一直在阅读并找到 this 答案,其中说托管驱动程序依赖于 tnsnames.ora 文件来解析别名,但是我依赖于 [=34= 中定义的 LDAP 服务器] 和 ldap.ora.
当我 tnsping 时,它说它使用 sqlnet.ora 来解析名称。
那么托管驱动程序为何不起作用?
ODP.NET 托管驱动程序依赖于配置文件中设置的 TNS_ADMIN 值来查找 TNSNAMES.ORA 文件。如果不想设置该值,可以在可执行文件的工作目录中包含 TNSNAMES.ORA 文件,或者直接在配置文件中创建 TNS 别名。
编辑:如果您依赖 SQLNET.ORA 和 LDAP.ORA,您也可以将它们复制到工作目录中,或者在您的配置文件中设置 LDAP 参数。有关可用于 LDAP 的配置文件参数,请参阅 ODP.NET 文档。
希望这个解决方法适合您。您可以自己查询LDAP服务器,并将完整的连接字符串放入您的代码中。
您可以使用以下代码解析来自 LDAP 的连接字符串:
using (OracleConnection connection = new OracleConnection())
{
connection.ConnectionString = "Data Source=" + ResolveServiceNameLdap("alias") + ";User id=user;Password=password";
connection.Open();
}
...
private static string ResolveServiceNameLdap(string serviceName)
{
string tnsAdminPath = Path.Combine(@"C:\oracle\network", "ldap.ora");
// or something more advanced...
// ldap.ora can contain many LDAP servers
IEnumerable<string> directoryServers = null;
if ( File.Exists(tnsAdminPath) ) {
string defaultAdminContext = string.Empty;
using ( var sr = File.OpenText(tnsAdminPath) ) {
string line;
while ( ( line = sr.ReadLine() ) != null ) {
// Ignore comments or empty lines
if ( line.StartsWith("#") || line == string.Empty )
continue;
// If line starts with DEFAULT_ADMIN_CONTEXT then get its value
if ( line.StartsWith("DEFAULT_ADMIN_CONTEXT") )
defaultAdminContext = line.Substring(line.IndexOf('=') + 1).Trim(new[] { '\"', ' ' });
// If line starts with DIRECTORY_SERVERS then get its value
if ( line.StartsWith("DIRECTORY_SERVERS") ) {
string[] serversPorts = line.Substring(line.IndexOf('=') + 1).Trim(new[] { '(', ')', ' ' }).Split(',');
directoryServers = serversPorts.SelectMany(x => {
// If the server includes multiple port numbers, this needs to be handled
string[] serverPorts = x.Split(':');
if ( serverPorts.Count() > 1 )
return serverPorts.Skip(1).Select(y => string.Format("{0}:{1}", serverPorts.First(), y));
return new[] { x };
});
}
}
}
// Iterate through each LDAP server, and try to connect
foreach ( string directoryServer in directoryServers ) {
// Try to connect to LDAP server with using default admin contact
try {
var directoryEntry = new DirectoryEntry("LDAP://" + directoryServer + "/" + defaultAdminContext, null, null, AuthenticationTypes.Anonymous);
var directorySearcher = new DirectorySearcher(directoryEntry, "(&(objectclass=orclNetService)(cn=" + serviceName + "))", new[] { "orclnetdescstring" }, SearchScope.Subtree);
SearchResult searchResult = directorySearcher.FindOne();
var value = searchResult.Properties["orclnetdescstring"][0] as byte[];
if ( value != null )
connectionString = Encoding.Default.GetString(value);
// If the connection was successful, then not necessary to try other LDAP servers
break;
} catch {
// If the connection to LDAP server not successful, try to connect to the next LDAP server
continue;
}
}
// If casting was not successful, or not found any TNS value, then result is an error
if ( string.IsNullOrEmpty(connectionString) )
throw new Exception("TNS value not found in LDAP");
} else {
// If ldap.ora doesn't exist, then throw error
throw new FileNotFoundException("ldap.ora not found");
}
return connectionString;
}