对 PUBLIC 数据库 Link 表的托管 ODP.NET 调用导致 TNS 错误
Managed ODP.NET Calls to PUBLIC Database Link tables cause TNS errors
我们的应用程序使用托管 ODP.NET 代码来调用各种 Oracle 过程。
对于我们的一位客户,他正在使用 public 数据库 links,并在过程中引用 linked tables,该调用失败.经进一步测试,任何通过 ODP.NET 到 linked table 的查询 运行 的任何尝试都会失败。
ORA-12154: TNS: could not resolve the connect identifier specified
全栈:
Oracle.ManagedDataAccess.Client.OracleException: ORA-12154: TNS:could not resolve the connect identifier specified
at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)
at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, OracleException& exceptionForArrayBindDML, Boolean isDescribeOnly, Boolean isFromEF)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior) at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at [APPLICATION CODE]
这很奇怪,因为尝试使用 SQL PLUS 进行相同的查询会成功,即使在客户端机器上没有 TNS 名称 ora 文件。
我们的应用甚至不使用 TNS 名称,它使用显式连接字符串详细信息。
我们甚至 运行 通过 DevArt oracle 驱动程序测试查询,它们也成功了。
好像专门通过托管 ODP.NET 进行调用会使服务器以不同方式对待数据库 link。
ODP.NET Query (Client Machine A) > (Server A) > Table (Server B) = ERROR
SQL Plus Query (Client Machine A) > (Server A) > Table (Server B) = SUCCESS
DEV ART Query (Client Machine A) > (Server A) > Table (Server B) = SUCCESS
我们现在使用的测试查询是一个简单的SELECT语句
有谁知道我们如何使 ODP.NET 以与 SQL Plus 相同的方式向服务器延迟此信息?
数据库Link信息:
OWNER: PUBLIC
USERNAME: [FIXED OTHER USER]
这肯定是服务器A和服务器B之间的连接(数据库link)有问题。
服务器 A 的 tnsnames.ora
文件中可能缺少服务器 B 的 TNS 名称,或者服务器 A 在其数据库中的 TNS 名称有误 link。
如果这是一个客户端问题,我可以想象收到一个 ORA-12154 错误,但不是 ORA-04088:如果您可以进入触发器,显然您与服务器 A 的连接没问题。
我会说下一步是直接连接到服务器 A 上的数据库,并尝试通过数据库 link 在服务器 B 上的数据库中查询 table。我希望这会因相同的 ORA-12154 错误而失败。
根据您的描述,可能是:
- 这是您的 ODP.NET 客户端的配置问题。
- 此外,客户端可能一开始就没有连接到服务器 A,更不用说连接到服务器 B 了。
事实上 ODP.NET 是一个瘦客户端(不涉及本机代码)应该排除与 Oracle 客户端二进制文件相关的任何配置:驱动程序是 "self contained" 并且完全独立于您的 SQLplus 安装.我的意思是,例如,如果你机器的 %path% 变量有错误,它影响的是数据的配置,而不是涉及的二进制文件。
可能 ODP.NET 可能看不到正确的 ORACLE_HOME
变量(例如 C:\u01\app\client\product.1.0\client_1\
)。并且配置错误可能表现为 ODP.NET 无法获取您的 TNS 名称文件的位置。
我建议:
- 尝试查看是否有另一个客户端首先出现在您的路径中,并隐藏了您的预期目录。例如:
C:\u01\app\client\product.2.1\client_1\bin;C:\u01\app\client\product.1.0\client_1\bin
。在这种情况下,作为快速尝试,请将您的 ODP.NET 客户端放在第一位。这很可能会使您的 ODP.NET 代码拾取指向右侧 Oracle_HOME 的指针,然后 TNS 配置将随之而来。对于复杂的设置,请考虑 Oracle 在注册表中存储不同的 ORACLE_HOME
值,并为您提供除 %path% 中的位置以外的其他方法 select 您想要的。如果感兴趣,请参阅 Using Multiple Oracle Homes。当然不要忘记把你的TNS名字放在正确的地方,这个TNS应该指向服务器A,而不是服务器B。
- 或者尝试创建一个对正在执行 ODP.NET 代码的 Windows 帐户可见的额外环境变量;此变量应称为
TNS_ADMIN
并指向 Oracle 客户端的管理目录(类似于 ..product.1.0\client_1\network\admin
)。通过从 ODP.NET 客户端发出 string tns_admin = Environment.GetEnvironmentVariable("TNS_ADMIN")
确保你的 ODP.NET 看到它。
- 如果以上方法不起作用,请尝试完全避免使用 TNS,在连接字符串中指定所有信息,如下所示:
<connectionStrings>
<add name="Server_A" connectionString="SERVER=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MyHost)(PORT=MyPort))(CONNECT_DATA=(SERVICE_NAME=MyOracleSID)));
uid=myUsername;pwd=myPassword;" />
</connectionStrings>
后者的设置如 here or the slightly different alternative here 所示。
我认为在您的 ODP.NET 配置中嵌入 TNS 信息实际上比依赖主机 OS 配置的任何其他解决方案更干净,因为使您的 ODP.NET 真正可移植(部署到测试或生产是更改 ODP.NET 配置的问题,而不是主机变量)。有关如何关联 TNS 和 ODP.NET 配置的更多示例,请参阅 Oracle Managed ODP.NET | Vijay's blog.
最后,SQLPlus 正常工作并不奇怪,因为它可能会看到不同的环境变量来获取连接标识符或 ORACLE_HOME
。
根本没有 tnsnames.ora 文件的事实可能表明
- 要么 sqlpus 是 运行 在命令行上指定连接,例如
sqlplus user/password@(description=(address_list=(address=.......ODS)))
(参见 Ask Tom "How to connect SQLPlus without tnsnames.ora")
- 或者 Oracle 实例驻留在同一台服务器上,因此严格来说不需要侦听器,您可以跳过 TNS 配置(参见 Connections That Require the Listener)
我们的客户端能够在服务器端解决这个问题。问题是数据库的格式 Link.
原始数据库Link:
CREATE PUBLIC DATABASE LINK [LINK_NAME]
CONNECT TO [USER]
IDENTIFIED BY [PASSWORD]
USING [TNS_NAME]
显然超过 ODP.NET,TNS 名称参考不足。一旦客户端切换到完整的连接细节,问题就解决了。
更新数据库 Link:
CREATE PUBLIC DATABASE LINK [LINK_NAME]
CONNECT TO [USER]
IDENTIFIED BY [PASSWORD]
USING
'(DESCRIPTION =
(SDU=[SDU])
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = [HOSTNAME])(PORT = [PORT]))
(CONNECT_DATA = (SID=[SID])
)'
此处有更多详细信息:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5005.htm
我们的应用程序使用托管 ODP.NET 代码来调用各种 Oracle 过程。
对于我们的一位客户,他正在使用 public 数据库 links,并在过程中引用 linked tables,该调用失败.经进一步测试,任何通过 ODP.NET 到 linked table 的查询 运行 的任何尝试都会失败。
ORA-12154: TNS: could not resolve the connect identifier specified
全栈:
Oracle.ManagedDataAccess.Client.OracleException: ORA-12154: TNS:could not resolve the connect identifier specified
at OracleInternal.ServiceObjects.OracleCommandImpl.VerifyExecution(OracleConnectionImpl connectionImpl, Int32& cursorId, Boolean bThrowArrayBindRelatedErrors, OracleException& exceptionForArrayBindDML, Boolean& hasMoreRowsInDB, Boolean bFirstIterationDone)
at OracleInternal.ServiceObjects.OracleCommandImpl.ExecuteReader(String commandText, OracleParameterCollection paramColl, CommandType commandType, OracleConnectionImpl connectionImpl, OracleDataReaderImpl& rdrImpl, Int32 longFetchSize, Int64 clientInitialLOBFS, OracleDependencyImpl orclDependencyImpl, Int64[] scnForExecution, Int64[]& scnFromExecution, OracleParameterCollection& bindByPositionParamColl, Boolean& bBindParamPresent, Int64& internalInitialLOBFS, OracleException& exceptionForArrayBindDML, Boolean isDescribeOnly, Boolean isFromEF)
at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior) at Oracle.ManagedDataAccess.Client.OracleCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader()
at [APPLICATION CODE]
这很奇怪,因为尝试使用 SQL PLUS 进行相同的查询会成功,即使在客户端机器上没有 TNS 名称 ora 文件。
我们的应用甚至不使用 TNS 名称,它使用显式连接字符串详细信息。
我们甚至 运行 通过 DevArt oracle 驱动程序测试查询,它们也成功了。
好像专门通过托管 ODP.NET 进行调用会使服务器以不同方式对待数据库 link。
ODP.NET Query (Client Machine A) > (Server A) > Table (Server B) = ERROR
SQL Plus Query (Client Machine A) > (Server A) > Table (Server B) = SUCCESS
DEV ART Query (Client Machine A) > (Server A) > Table (Server B) = SUCCESS
我们现在使用的测试查询是一个简单的SELECT语句
有谁知道我们如何使 ODP.NET 以与 SQL Plus 相同的方式向服务器延迟此信息?
数据库Link信息:
OWNER: PUBLIC
USERNAME: [FIXED OTHER USER]
这肯定是服务器A和服务器B之间的连接(数据库link)有问题。
服务器 A 的 tnsnames.ora
文件中可能缺少服务器 B 的 TNS 名称,或者服务器 A 在其数据库中的 TNS 名称有误 link。
如果这是一个客户端问题,我可以想象收到一个 ORA-12154 错误,但不是 ORA-04088:如果您可以进入触发器,显然您与服务器 A 的连接没问题。
我会说下一步是直接连接到服务器 A 上的数据库,并尝试通过数据库 link 在服务器 B 上的数据库中查询 table。我希望这会因相同的 ORA-12154 错误而失败。
根据您的描述,可能是:
- 这是您的 ODP.NET 客户端的配置问题。
- 此外,客户端可能一开始就没有连接到服务器 A,更不用说连接到服务器 B 了。
事实上 ODP.NET 是一个瘦客户端(不涉及本机代码)应该排除与 Oracle 客户端二进制文件相关的任何配置:驱动程序是 "self contained" 并且完全独立于您的 SQLplus 安装.我的意思是,例如,如果你机器的 %path% 变量有错误,它影响的是数据的配置,而不是涉及的二进制文件。
可能 ODP.NET 可能看不到正确的 ORACLE_HOME
变量(例如 C:\u01\app\client\product.1.0\client_1\
)。并且配置错误可能表现为 ODP.NET 无法获取您的 TNS 名称文件的位置。
我建议:
- 尝试查看是否有另一个客户端首先出现在您的路径中,并隐藏了您的预期目录。例如:
C:\u01\app\client\product.2.1\client_1\bin;C:\u01\app\client\product.1.0\client_1\bin
。在这种情况下,作为快速尝试,请将您的 ODP.NET 客户端放在第一位。这很可能会使您的 ODP.NET 代码拾取指向右侧 Oracle_HOME 的指针,然后 TNS 配置将随之而来。对于复杂的设置,请考虑 Oracle 在注册表中存储不同的ORACLE_HOME
值,并为您提供除 %path% 中的位置以外的其他方法 select 您想要的。如果感兴趣,请参阅 Using Multiple Oracle Homes。当然不要忘记把你的TNS名字放在正确的地方,这个TNS应该指向服务器A,而不是服务器B。 - 或者尝试创建一个对正在执行 ODP.NET 代码的 Windows 帐户可见的额外环境变量;此变量应称为
TNS_ADMIN
并指向 Oracle 客户端的管理目录(类似于..product.1.0\client_1\network\admin
)。通过从 ODP.NET 客户端发出string tns_admin = Environment.GetEnvironmentVariable("TNS_ADMIN")
确保你的 ODP.NET 看到它。 - 如果以上方法不起作用,请尝试完全避免使用 TNS,在连接字符串中指定所有信息,如下所示:
<connectionStrings>
<add name="Server_A" connectionString="SERVER=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=MyHost)(PORT=MyPort))(CONNECT_DATA=(SERVICE_NAME=MyOracleSID)));
uid=myUsername;pwd=myPassword;" />
</connectionStrings>
后者的设置如 here or the slightly different alternative here 所示。
我认为在您的 ODP.NET 配置中嵌入 TNS 信息实际上比依赖主机 OS 配置的任何其他解决方案更干净,因为使您的 ODP.NET 真正可移植(部署到测试或生产是更改 ODP.NET 配置的问题,而不是主机变量)。有关如何关联 TNS 和 ODP.NET 配置的更多示例,请参阅 Oracle Managed ODP.NET | Vijay's blog.
最后,SQLPlus 正常工作并不奇怪,因为它可能会看到不同的环境变量来获取连接标识符或 ORACLE_HOME
。
根本没有 tnsnames.ora 文件的事实可能表明
- 要么 sqlpus 是 运行 在命令行上指定连接,例如
sqlplus user/password@(description=(address_list=(address=.......ODS)))
(参见 Ask Tom "How to connect SQLPlus without tnsnames.ora") - 或者 Oracle 实例驻留在同一台服务器上,因此严格来说不需要侦听器,您可以跳过 TNS 配置(参见 Connections That Require the Listener)
我们的客户端能够在服务器端解决这个问题。问题是数据库的格式 Link.
原始数据库Link:
CREATE PUBLIC DATABASE LINK [LINK_NAME]
CONNECT TO [USER]
IDENTIFIED BY [PASSWORD]
USING [TNS_NAME]
显然超过 ODP.NET,TNS 名称参考不足。一旦客户端切换到完整的连接细节,问题就解决了。
更新数据库 Link:
CREATE PUBLIC DATABASE LINK [LINK_NAME]
CONNECT TO [USER]
IDENTIFIED BY [PASSWORD]
USING
'(DESCRIPTION =
(SDU=[SDU])
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = [HOSTNAME])(PORT = [PORT]))
(CONNECT_DATA = (SID=[SID])
)'
此处有更多详细信息:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5005.htm