Friday, May 15, 2009

Oracle (Not So) Instant Client

Developing database oriented .NET applications is quite a no brainer once you are used to your API (ADO.NET, Enterprise Library Data Application Block…) or your ORM (NHibernate…). Just pick your database vendor ADO .NET provider, which usually consists of one assembly that you distribute with your application, and that’s it. That is how it works with SQL Server (of course), but also TeradataMySQL, PostgreSQL, SQLite… You name it, that is the way it works.

Oracle, you said ? Well, that must be the exception that confirms the rule (along with DB2, but I would like to focus my rant on Oracle, if I may). It works quite like this. You need an ADO .NET provider AND a native client. The problems with this are :

But thanks to the Oracle guys, there is another solution : Oracle Instant Client. It consists of just a few DLLs that you can distribute along with your application and that allow connection to Oracle databases. If you are not too picky about character sets and supported languages, you can trim it down do 19Mb.

I became quite efficient with this : I add the necessary files as Content in my Visual Studio project, and a simple Setup project makes a good enough installer for my solution.

Then this week, a client called me about how one of these applications just crashed after he installed it on a Windows 2008 Server. The fact was : it was a 64 bits Windows. As the .NET application was compiled as AnyCPU, it was automatically launched as a 64 bits application, and then you can guess by yourself what happened when the 32 bits Oracle client was loaded in memory. At this point, my options were :

  • compile the application as x86, so that it would be launched as a 32 bits application even on a x64 platform. But why tweak my application when it is really Oracle that was at fault ?
  • leave the application as AnyCPU, but distribute a different client depending on the platform it is installed on. This is the (more interesting) road I took.

I decided to give up on the standard Setup project (had I any other option ?) and use WiX 3.0 instead. My first attempt was to use a single installer to be used on both platforms (x86 and x64), and distribute the Oracle Instant Client 11 depending on the platform (I found that version 10 made my application crash on Windows Vista x64, while version 11 worked fine…). This is how I achieved this :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<?if $(sys.BUILDARCH) = "x86" ?>
<Component Id="OracleInstantClientFiles_x86" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR NOT VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x86_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\oraociei11.dll" DiskId="1" />
</Component>
<?endif ?>
<Component Id="OracleInstantClientFiles_x64" Guid="{3CCDBDB6-D45A-4523-8CC7-730D3A8851D3}" KeyPath="yes">
<Condition>
<![CDATA[Installed OR VersionNT64]]>
</Condition>
<File Id="OracleInstantClientFiles_x64_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_x64_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x64\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

<Fragment>
<ComponentGroup Id="OracleInstantClientFiles">
<?if $(sys.BUILDARCH) = "x86" ?>
<ComponentRef Id="OracleInstantClientFiles_x86" />
<?endif ?>
<ComponentRef Id="OracleInstantClientFiles_x64" />
</ComponentGroup>
</Fragment>

</Wix>

This works alright. Just reference the OracleInstantClientFiles component and there you have it. But I was annoyed by the fact that my application would install in the Program Files (x86) folder by default. So I went with the dual installer solution. And the source code became :

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="OracleInstantClientFiles" Guid="{AA0076CE-F7B6-4cd8-9B67-199F665A8E77}" KeyPath="yes">
<?if $(sys.BUILDARCH) = "x86" ?>
<File Id="OracleInstantClientFiles_x86_msvcr71.dll" Name="msvcr71.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\x86\msvcr71.dll" DiskId="1" />
<?endif ?>
<File Id="OracleInstantClientFiles_oci.dll" Name="oci.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oci.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_orannzsbb11.dll" Name="orannzsbb11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\orannzsbb11.dll" DiskId="1" />
<File Id="OracleInstantClientFiles_oraociei11.dll" Name="oraociei11.dll" Source="$(sys.CURRENTDIR)..\..\lib\Oracle\InstantClient\$(sys.BUILDARCH)\oraociei11.dll" DiskId="1" />
</Component>
</DirectoryRef>
</Fragment>

</Wix>

So now I can distribute the correct Oracle files (worth 130Mb per platform) along with my less than 2Mb application !

3 comments:

Trey @ CALFIRE said...

Thanks for this post. It made clear some things that I was starting to run into and didn't see solid documentation about.

I am trying to deploy a .Net application via ClickOnce and would like to include the Instant Client (shaved as much as possible).

Rather than install it on a user's computer via their batch file and modifying the registry or path statement... is there any way to safely package them in one's application? such as referencing only the Oracle.DataAccess.dll and the other three your linked article references and setting them to "copy local"?

twhite @ fire . ca . gov

thanks.

Mac said...

The main benefit of Oracle Instant Client is that you do not have to install it. If you can get rid of TNSNAMES.ORA or SQLNET.ORA files, you will just have to distribute the DLLs along with your application binary (cf. Dynamic-Link Library Search Order).

one.beat.consumer said...

Hey Mac-

So I been keeping myself busy with this application. Never wrote back, but I have got the oracle 11 instant client working for my WPF application using ClickOnce for deployment.

Always a couple snares... it bails on Windows 2003 and Windows 7 right at first load... i never see visuals just a crap out XamlParse error I can snatch and get the stack errors from.

Turns out the last piece of my code it says threw an exception is my CRUD code, saying there "is no Table[0]" in my return... it works flawlessly outside of this so it has to be something with the managed Oracle code.

You know anything about this? Had any other compatibility problems with these OS's and Oracle's Instant Client?

It should have nothing to do with ClickOnce, all the DLLs for the client get dumped in the app root so path or permissions with ClickOnce should be moot issues too.

I'd love any feedback. My 2003 experiment was a 32-bit install so your trick of compiling only in 32bit didn't solve it.

Windows 7 that might be it. Gonna test tomorrow.

Thanks, Mac...

-Trey @ CALFIRE