0

I have 32-bit drivers installed on my box (they were installed and configured by some DBAs)

I wrote a simple script to test the drivers which pretty much is as follows

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());
        }
    }
}

When I compile this application as 32-bit with the 32-bit Oracle.DataAccess.dll, it executes without a hitch.

However if I compile the application as AnyCPU with the Oracle.ManagedDataAccess.dll, i get an ORA-12154 (could not resolve the connect identifier specified) error.

If I tnsping alias, it works correctly and tells me the connect identifier with the real database name.

If I then change the connection string to use the real database name instead of the alias, and then try with the managed library again, it executes without a hitch.

I've been reading around and found this answer which says the managed driver relies on the tnsnames.ora file to resolve the aliases, however I rely on LDAP servers defined in sqlnet.ora and ldap.ora.

When I tnsping, it says it uses sqlnet.ora to resolve the name.

So how come the managed drivers do not work?

Brad Zacher
  • 2,915
  • 18
  • 31
  • What is the value of your `TNS_ADMIN` environment variable (if existing)? What is the value in your registry? Check `HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_{YOUR_ORACLE_HOME}\TNS_ADMIN` and `HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\ORACLE\KEY_{YOUR_ORACLE_HOME}\TNS_ADMIN` – Wernfried Domscheit Jun 18 '15 at 06:53
  • @Wernfried there isn't one in the registry in either of those locations, but there is an environment variable which points at c:\Oracle\network, which is the folder where my sqlnet.ora and ldap.ora files are stored. – Brad Zacher Jun 18 '15 at 06:57
  • Run the [Process Monitor](https://technet.microsoft.com/en-us/sysinternals/bb896645.aspx?f=255&MSPPError=-2147217396) from Windows Sysinternals and check where the application searches your sqlnet.ora and ldap.ora files. – Wernfried Domscheit Jun 18 '15 at 07:03
  • @Wernfried according to procmon, tnsping accesses C:\oracle\network\sqlnet.ora, and C:\oracle\network\ldap.ora. additonally, according to procmon - my test app also accesses those same two files and reads them successfully to the end, only then does it try to access tnsnames.ora (which fails) – Brad Zacher Jun 18 '15 at 07:43
  • Maybe as a workaround install Oracle Client 32 bit and 64 bit, see [Stack Overflow - Install Oracle x86 and x64](http://stackoverflow.com/questions/24104210/badimageformatexception-this-will-occur-when-running-in-64-bit-mode-with-the-32#24120100). However, ODP.NET Managed Driver is intended to work without any Oracle client installation, so looks like a bug in Oracle. – Wernfried Domscheit Jun 18 '15 at 08:47
  • The managed driver does not use environment variables or the registry. TNSPING does. That is the difference. Look up the LDAP config file parameters available to the managed driver in the ODP.NET doc and set them to similar values as you have in your SQLNET.ORA. – Christian Shay Jun 18 '15 at 18:58
  • @Wernfried Both are already installed! I've setup my VS project with a project each for 32-bit, 64-bit and managed drivers. It's just the managed driver that fails. I've done some more research and enabled tracing - it's really weird how the managed driver has different tracing output to the unmanaged. The managed driver actually prints *ALL* of the tns names it fetches from the LDAP server. I've searched that list and the aliases don't exist, but the real names do. so it must be a bug in ODP.NET Managed. – Brad Zacher Jun 19 '15 at 02:16
  • @ChristianShay I've decompiled and perused the source of the managed DLL using JetBrains dotPeek and i can tell you it definitely does use environment variables (but I didn't see any registry reading). From the trace log, I can see that the managed drivers are connecting to and reading from the LDAP servers fine, but they're only getting the real names, not aliases. – Brad Zacher Jun 19 '15 at 02:23
  • @Wernfried & @ChristianShay - i've found the problem! By decompiling the code I've found that ODP.NET uses the standard System.DirectoryServices.Protocols.LdapConnection to connect to the server. It creates a System.DirectoryServices.Protocols.SearchRequest object to search for the database connection string on the server. HOWEVER - they do not tell the request how to handle aliases, so it just ignores they exist. they need to set ``searchRequest.Aliases = DereferenceAlias.Always`` so that the connection knows to not ignore them! – Brad Zacher Jun 19 '15 at 03:31
  • Bug report posted on their forums: https://community.oracle.com/message/13141200 – Brad Zacher Jun 22 '15 at 01:37
  • If it does use environment variables (I am not aware of this), it is not documented behavior and you should not rely on it. – Christian Shay Jun 22 '15 at 13:35
  • You said you rely on a LDAP server. How does the `ldap.ora` file look like? We have similar issue and the solution was to put the server name in brackets, i.e. `DIRECTORY_SERVERS = (oid.domain.com:389)` instead of `DIRECTORY_SERVERS = oid.domain.com:389` - other drivers do not require the brackets. When you have more than one name server, then brackets are required in any case. – Wernfried Domscheit Apr 16 '20 at 12:25

2 Answers2

1

May this workaround is suitable for you. You can query the LDAP server by your own and put the full connection string to your code.

You can resolve the connection string from LDAP with this code:

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;
  }
Wernfried Domscheit
  • 54,457
  • 9
  • 76
  • 110
  • Any reason for down vote? Problem is that ODP.NET Managed Provider does not resolve name from LDAP, so bypass it and resolve the name by yourself. Yes, it is a workaround but it should work. – Wernfried Domscheit Jun 19 '15 at 05:53
  • I don't understand the downvote either. I have submitted a bug report to oracle (https://community.oracle.com/message/13141200) as (in my opinion) this is a bug with the managed driver. For the time being I'll stick with using the unaliased name as I'd prefer config changes over code changes if oracle comes back and says they'll fix it. Though if they say it's an intended feature, your function will go straight into my libraries. – Brad Zacher Jun 22 '15 at 01:31
  • This is amazing and works beautifully on .NET core 5+. You will need the new drivers from Oracle: (nuget library Oracle.ManagedDataAccess.Core) https://www.nuget.org/packages/Oracle.ManagedDataAccess.Core/ – Rob Aug 01 '23 at 20:59
  • @Rob as you see this answer is 8 years old - hopefully Oracle fixed this bug meanwhile. – Wernfried Domscheit Aug 01 '23 at 21:38
0

ODP.NET Managed driver relies on TNS_ADMIN value being set in the config file to find the TNSNAMES.ORA file. If you don't want to set that value, you can include the TNSNAMES.ORA file in the working directory of the executable or create a TNS alias in the config file directly.

Edit: If you are relying on SQLNET.ORA and LDAP.ORA you can also copy those into the working directory as well, or set the LDAP parameters in your config file. See the ODP.NET doc for the config file parameters available for LDAP.

Christian Shay
  • 2,570
  • 14
  • 24
  • how come the x86 driver works fine and is able to resolve the alias, but the managed driver cannot? Why doesn't the managed driver use the sqlnet.ora file and figure out it should resolve the alias via LDAP? – Brad Zacher Jun 18 '15 at 05:42
  • The managed driver doesn't know where the sqlnet.ora file is! With the unmanaged driver it does. Have a look in the doc at config file parameters that are possible with ODP.NET, Managed. I believe you can set one in there for LDAP. – Christian Shay Jun 18 '15 at 05:43
  • alright, thanks. So my box currently has the TNS_ADMIN environment variable set to "C:\Oracle\network", which is where the sqlnet.ora, and ldap.ora files are. So how can I make the managed driver find these as well? – Brad Zacher Jun 18 '15 at 06:27
  • According to https://docs.oracle.com/html/E41125_02/featConfig.htm#BABEGGHD, the TNS_ADMIN directory can be configured in the app.config file. I've added to the oracle.managaeddataacess.client/version/settings section, but it still gives me ORA-12154. Is there something I'm missing? – Brad Zacher Jun 18 '15 at 06:37