.NET アプリでデータベースに接続するのさらに続き

# はじめに

自分は仕事で Oracle データベースに接続する .NET アプリを書いています。と言っても、前任から引継したプログラムを改修しているので、データベースに接続するコードを自分で書いたことはありません。
客先の環境でエラー発生してデータベースに接続できないと、不具合の報告を貰うことがあります。同じプログラムが開発環境や別の端末で正しく動作するので、環境に依存するようです。
どんな環境は OK で、どんな環境は NG なのか、分からなくて苦しみました。当時の試行錯誤を、改めて確認しておこうと思いました。

# Oracle.DataAccess.Client を使ってデータベースに接続する

[前回の記事](https://qiita.com/tinymouse/items/ac9a0c798d3f36cf02a3) で、`System.Data.OracleClient` または `Oracle.ManagedDataAccess.Client` を使ってデータベースに接続するプログラムを書きました。
仕事で引継したプログラムは、`Oracle.DataAccess.Client` を使ってデータベースに接続するようになっていました。

## .NET Framework＋Oracle.DataAccess.Client で接続する

.NET Framework アプリを作って Oracle データベースに接続してみます。

参考：[【C#】【Oracle】Oracle.DataAccess.dllを利用してデータを取得。その１ | 創造的プログラミングと粘土細工](http://pro.art55.jp/?eid=1304041)

Visual Studio で「新しいプロジェクトの作成」して「コンソールアプリ (.NET Framework）」を選択します。
フレームワークは `.NET Framework 4.5.2` を選択します。

`System.Data.OracleClient` と違って、`Oracle.DataAccess.Client` は、.NET Framework に含まれていません。
そこで、Oracle のウェブサイトから接続用ライブラリを入手します。

[ODP.NET（管理対象外 ）のインストール | 節約プログラマー雑記](https://monoaso.net/2021/09/21/oracle003/)

その上で、プロジェクトの「参照」に `Oracle.DataAccess.Client` を追加します。

以下のコードを実行してみます。

```csharp
using Oracle.DataAccess.Client;
            
string conStr = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)));User id=scott;Password=tiger;";

try
{
    using (OracleConnection cn = new OracleConnection())
    {
        cn.ConnectionString = conStr;
        cn.Open();
        Console.WriteLine("Connected.");
    }
}
catch (Exception ex) {
    Console.WriteLine("Error: " + ex.Message);
}
Console.ReadKey();
```

## 動作を確認する環境を用意する

作成した .NET アプリがデータベースに接続できるか確認する環境を用意します。仕事で使用していた環境に近いものを用意しようと思います。

・開発環境
Windows 10 
Visual Studio 2017
ODAC 11g および 12c [32 ビット版] および [64 ビット版]

・接続元クライアント機
Windows 10 [64 ビット] および Windows XP [32 ビット]
Oracle Client 11g および 12c [32 ビット版] および [64 ビット版]

・接続先データベースサーバ
Windows 10
Oracle Database 11g XE

### ODAC と Oracle Client

`Oracle Data Access Components (ODAC)` は、一連のWindowsおよび.NETデータ・アクセス・ドライバおよびツールです。.NET、COM および ODBC データ・アクセスのサポート、アプリケーションを開発するための Microsoft Visual Studio ツール、ASP.NET プロバイダ および .NET ストアド・プロシージャ・サポートが含まれます。

ODAC は Oracle が作成して配布しています。`Oracle Client` を入手してインストールすることで、開発機やクライアント機に導入できます。

[Oracle Database 12c Client インストール - Project Group](https://www.projectgroup.info/documents/Oracle/ORA-120006.html)

### 開発機を準備する

開発機で、.NET アプリに組込する `ODAC` ライブラリは以下を切替して確認したい。

* 11g [32 ビット版]
* 11g [64 ビット版]　←今回は使わない
* 12c [32 ビット版]
* 12c [64 ビット版]

「OUI（Oracle Universanl Installer) 版」にしても「XCOPY 版」にしても、インストール先の Windows 環境の GAC やレジストリを更新する。一旦インストールすると、アンインストールしても Windows 環境の変更が完全に戻らない。
Oracle Client がインストールされたことのない Windows が、上記のパターンだけ欲しくなる。

実機を揃えるわけにはいかないし、クラウドサービスの仮想マシンを使ってもよかったのだが、１台の開発機で済ませてみた。
ODAC のライブラリ（実態は DLL ファイル）を、.NET アプリの「参照」に設定できればいいわけだ。
「XCOPY 版」のパッケージ（実態は ZIP ファイル）をダウンロードして解凍する。インストーラを使うとレジストリなど書込されてしまうので、解凍した内容をそのまま使う。
上記のパターンごとに 4 つのフォルダを用意して、使用したいライブラリを「参照」設定する。

### クライアント機を準備する

接続元のクライアント機で、`Oracle Client` は以下を切替して確認したい。

* 11g [32 ビット版]
* 11g [64 ビット版]
* 11g [32 ビット版]＋[64 ビット版] 混在　←今回は使わない
* 12c [32 ビット版]
* 12c [64 ビット版]
* 12c [32 ビット版]＋[64 ビット版] 混在

Oracle Client は、一旦インストールすると、アンインストールしても Windows 環境の変更が完全に戻らない。
Oracle Client がインストールされたことのない Windows が、上記のパターンだけ欲しくなる。

実機を揃えるわけにはいかないので困っていたが、`Windows サンドボックス` が使えるのでないかと思った。

[Windows Sandboxを積極的に活用しよう｜Kernel](https://note.com/kernel_91/n/n5b6f9193ea0b)

ところが困ったことに、サンドボックスで起動した Windows で Oracle Client のインストーラを起動すると、「インストーラの検証実行に必要な最初の手順が失敗しました」エラーになってインストールできない。

[Windows10にOracle Clientをインストールするとき詰まった話 #oracle - Qiita](https://qiita.com/meronmks/items/dfabdfce7679076c2333)

サンドボックスの Windows アカウントは管理者権限がないのが問題のようだ。

実機を揃えるわけにはいかないので、クラウドサービスの仮想マシンを使うことにした。

Google Compute Engine を使って利用できる Windows 機は Windows Server なので、使いたかった Windows 10 と厳密に同じでないが仕方ない。

ところがそのせいか、インストーラが途中で落ちる。

[(Oracle 12.1.0.2 32bit clientを導入する場合の障害について［2016/01/05］)](https://xupper.jbcc.co.jp/support/contents.asp?P=388&I=4863)

苦労したが、複数の Oracle Client を導入したクライアント機を用意できた。

Windows XP [32 ビット] のクライアント機は、実機を用意した。

### サーバ機を準備する

Oracle データベースをインストールするのは、開発機でもいいかと思ったが、アプリに組込するライブラリに影響するといけないので、別に準備した。
当時の環境は Windows Server 2012 R2 だったが、空いていた Windows 10 機を使った。

Oracle Database 11g は、無償版の Express Edition を使った。

併せて、Oracle Database 21c Express Edition も用意した。
こちらはインストールして途中でエラー表示されたりして非常に時間が掛かった。

参考：[OracleDatabase21cExpressEditionをイ... - Yahoo!知恵袋](https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13286568123)

Oracle Client 11g や 12c で Oracle Database 21c に接続できるか確認してみたが、OK だった。

## Oracle.DataAccess.Client でデータベースに接続する①

前述した手順で Oracle データベースに接続する .NET アプリを作成します。

.NET アプリに組込する `Oracle.DataAccess` ライブラリは `12c [64 ビット版]` を選びました。
開発機やクライアント機が `[64 ビット] 機` だからです。
接続先のデータベースは `11g` ですが、新しいバージョンがいいだろうと考えました。

### Oracle.DataAccess 12c [64]＋Windows 10 [64]＋Oracle Client なし・・①

まず、Oracle Client をインストールしていないクライアント機で実行してみます。

```
'Oracle.DataAccess.Client.OracleConnection' のタイプ初期化子が例外をスローしました。
```

**動作：×**

この .NET アプリは開発機で動作しています。違うのは、開発機は Oracle Client がインストールされていることです。
`Oracle.DataAccess` は `Oracle.ManagedDataAccess` と違って、Oracle データベースと直接接続しないで、OS にインストールされた Oracle Client を呼出しているようです。

### Oracle.DataAccess 12c [64]＋Windows 10 [64]＋Oracle Client 12c [64]・・②

上記のクライアント機に `Oracle Client 12c [64 ビット版]` をインストールします。

接続できました。

**動作：○**

![説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114460/fa61a0d7-5eb5-6880-fa3e-c69387cc7b8c.png)

## Oracle.DataAccess.Client でデータベースに接続する②

一部のクライアント機は `Windows XP [32 ビット版]` が残っていると、顧客に言われました。

### Oracle.DataAccess 12c [64]＋Windows XP [32]＋Oracle Client 12c [32]・・③

クライアント機に導入するのは `Oracle Client 12c [32 ビット版]` になります。
作成した .NET アプリはそのままで実行してみます。

```
'Oracle.DataAccess.Client.OracleConnection' のタイプ初期化子が例外をスローしました。
```

**動作：×**

### Oracle.DataAccess 12c [32]＋Windows XP [32]＋Oracle Client 12c [32]・・④

.NET アプリに組込するライブラリを `[32 ビット版]` に変更してみます。

接続できました。

**動作：○**

## Oracle.DataAccess.Client でデータベースに接続する③

大半のクライアント機は `Windows 10 [64 ビット版]` です。

### Oracle.DataAccess 12c [32]＋Windows 10 [64]＋Oracle Client 12c [32]・・⑤

クライアント機にインストールする Oracle Client を、.NET アプリに組込したライブラリに合わせて `12c [32 ビット版]` に変更しました。

```
ハンドルされていない例外: System.BadImageFormatException: ファイルまたはアセンブリ 'Oracle.DataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342'、またはその依存関係の 1 つが読み込めませんでした。間違ったフォーマットのプログラムを読み込もうとしました。
```

**動作：×**

### Oracle.DataAccess 12c [32]＋Windows 10 [64]＋Oracle Client 12c [64]・・⑥

クライアント機にインストールする Oracle Client を、クライアント機に合わせて `[64 ビット版]` に変更しました。

接続できました。

**動作：○**

.NET アプリに組込したライブラリとクライアント機に導入する Oracle Client は一致していなくていいのでしょうか。

## Oracle.DataAccess.Client でデータベースに接続する④

引継した .NET アプリの一部はサードパーティのライブラリを組込していました。そのライブラリは `[32 ビット版]` だけで `[64 ビット版]` が提供されていませんでした。
`[32 ビット版] Windows` で実行できればいいのですが、`[64 ビット版] Windows` で実行しないといけません。

```
プログラムは動作を停止しました。
```

.NET アプリを `[32 ビット] モード` で実行すればいいようです。.NET アプリの「ターゲット CPU」の指定が `AnyCPU` だったものを `x86` に変更しました。

[.NETにおける64ビットプロセスと32ビットプロセスについて #Windows - Qiita](https://qiita.com/mima_ita/items/57d7c1101543e214b1d6)

### x86 指定＋Oracle.DataAccess 12c [32]＋Windows 10 [64]＋Oracle Client 12c [64]・・⑦

`x86` 指定してビルドし直した .NET アプリを実行してみます。

```
'Oracle.DataAccess.Client.OracleConnection' のタイプ初期化子が例外をスローしました。
```

**動作：×**

### x86 指定＋Oracle.DataAccess 12c [32]＋Windows 10 [64]＋Oracle Client 12c [32]・・⑧

クライアント機にインストールする Oracle Client を、.NET アプリの実行モードに合わせて `[32 ビット版]` に変更しました。

接続できました。

**動作：○**

## Oracle.DataAccess.Client でデータベースに接続する⑤

一部の .NET アプリは Windows Server 2003 で実行しないといけなくなりました。

`Oracle Client 12c` は Windows Server 2003 Server で動作しませんでした。

参考：[Oracle Database Clientインストレーション・ガイド12cリリース1(12.1) for Microsoft Windows](https://docs.oracle.com/cd/E57425_01/121/NTCLI/toc.htm)

`Oracle Client 11g` なら Windows Server 2003 Server で動作するようです。

参考：[Oracle Database Clientインストレーション・ガイド, 11g リリース2 (11.2) for Microsoft Windows](https://docs.oracle.com/cd/E16338_01/install.112/b58879/toc.htm)

### x86 指定＋Oracle.DataAccess 12c [32]＋Windows 10 [64]＋Oracle Client 11g [32]・・⑨

クライアント機にインストールする Oracle Client を `11g` にしました。

```
ハンドルされていない例外: System.BadImageFormatException: ファイルまたはアセンブリ 'Oracle.DataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342'、またはその依存関係の 1 つが読み込めませんでした。間違ったフォーマットのプログラムを読み込もうとしました。
```

**動作：×**

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 11g [32]・・⑩

.NET アプリに組込するライブラリを、Oracle Client に合わせて `11g` に変更しました。

接続できました。

**動作：○**

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 12c [32]・・⑪

クライアント機にインストールする Oracle Client を `12c` に戻してみました。

接続できました。

**動作：○**

## Oracle.DataAccess.Client でデータベースに接続する⑥

上記のクライアント機で Oracle Client が `32 ビット版` に加えて `64 ビット版` が必要になりました。
Microsoft Access で ODBC 経由で Oracle データベースに接続しているそうです。Access が `32 ビット版` のときは Oracle Client（それに含まれる ODBC ドライバ）は `32 ビット版` が必要で、Access が `64 ビット版` になると ODBC ドライバ `64 ビット版` が必要になるそうです。

参考：[雑学の箱 ｜ みどり情報技術](https://midojo.jp/m_box.php?topic=6)

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 12c [32]＋[64]・・⑫

Oracle Client `32 ビット版` と `64 ビット版` が混在する環境になりました。

接続できました。

**動作：○**

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 12c [32]＋[64]←64 指定・・⑬

別のアプリがデータベースに接続できなくなったと顧客から報告ありました。調べたところ `ORACLE_HOME` の指定すれば直るようです。

[Oracle 接続できない…よくある事例と対処法](https://products.sint.co.jp/siob/blog/oracle-access)

Oracle Client [64 ビット版] のインストール先を `ORACLE_HOME` に指定しました。

```
ORA-12557: TNS: プロトコル・アダプタをロードできません
```

**動作：×**

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 12c [32]＋[64]←32 指定・・⑭

Oracle Client [32 ビット版] のインストール先を `ORACLE_HOME` に指定しました。

接続できました。

**動作：○**

### x86 指定＋Oracle.DataAccess 11g [32]＋Windows 10 [64]＋Oracle Client 12c [64]・・⑮

.NET アプリに組込するライブラリは `[32 ビット版]` ＋クライアント機の Oracle Client は `[64 ビット版]` で OK でした（上記⑥）。`[x86] 指定` した .NET アプリは、どうなるでしょうか。

```
'Oracle.DataAccess.Client.OracleConnection' のタイプ初期化子が例外をスローしました。
```

**動作：×**

.NET アプリが `[32 ビット] モード` で実行されるなら、呼出される Oracle Client は `[32 ビット版]` でなければならないようです。

## 確認の結果のまとめ

以上の試行錯誤を表にまとめてみます。

|    |    | Oracle.DataAccess | OS | Oracle client | 動作 |
|:---:|:---|:---|:---|:---|:---:|
| ① | AnyCPU | 12c [64] | Windows 10 [64] | **なし** | × |
| ② | AnyCPU | 12c [64] | Windows 10 [64] | 12c [64] | ○ |
| ③ | AnyCPU | 12c [64] | Windows XP **[32]** | 12c [32] | × |
| ④ | AnyCPU | 12c **[32]** | Windows XP [32] | 12c [32] | ○ |
| ⑤ | AnyCPU | 12c [32] | Windows 10 **[64]** | 12c [32] | × |
| ⑥ | AnyCPU | 12c [32] | Windows 10 [64] | 12c **[64]** | ○ |
| ⑦ | **x86** | 12c [32] | Windows 10 [64] | 12c [64] | × |
| ⑧ | x86 | 12c [32] | Windows 10 [64] | 12c **[32]** | ○ |
| ⑨ | x86 | 12c [32] | Windows 10 [64] | **11g** [32] | × |
| ⑩ | x86 | **11g** [32] | Windows 10 [64] | 11g [32] | ○ |
| ⑪ | x86 | 11g [32] | Windows 10 [64] | **12c** [32] | ○ |
| ⑫ | x86 | 11g [32] | Windows 10 [64] | 12c **[32]＋[64]** | ○ |
| ⑬ | x86 | 11g [32] | Windows 10 [64] | 12c [32]＋[64]**←64** | × |
| ⑭ | x86 | 11g [32] | Windows 10 [64] | 12c [32]＋[64]**←32** | ○ |
| ⑮ | x86 | 11g [32] | Windows 10 [64] | 12c **[64]** | × |

ルールが分かってくればまだしも当時は分かっていなかったので、作成している .NET アプリと別の都合で実行環境が変化するたびにデータベースに接続できなくなって、対応に悩まされました。

# Oracle.ManagedDataAccess.Client を使って接続する

`Oracle.DataAccess` ライブラリを使うと上記のように、インストールする Oracle Client の状況に依存して、同じ .NET アプリが接続できたりできなくなったりします。
これを回避するために、どうしたらいいでしょうか。

`Oracle.ManagedDataAccess` ライブラリを使えばいいようです。

[【.net】Oracleクライアントインストールなしで接続する方法 | 水戸スヤのSE備忘録](https://mitosuya.net/connect-oracle-without-install)

`Oracle.ManagedDataAccess` は `Oracle.DataAccess` と違って、OS にインストールされた Oracle Client を使わずに、Oracle データベースと直接接続するようです。

引継したプログラムが `Oracle.DataAccess` ライブラリを使っていたので、そのまま使い続けてしまいましたが、`Oracle.ManagedDataAccess` ライブラリに切替するのは、簡単だったようです。

[ODP.NET Managed Driver(管理対象ドライバ)についての私的まとめ - しばたテックブログ](https://blog.shibata.tech/entry/2014/11/02/173039)

# 接続先を記述する

[前回の記事](https://qiita.com/tinymouse/items/ac9a0c798d3f36cf02a3) で、接続先などを指定する接続文字列は以下のように書いています。

```
string conStr = "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)));User id=scott;Password=tiger;";
```

参考：[ODP.NET の接続文字列 - tsucchi’s diary(元はてなダイアリー)](https://tsucchi.hatenadiary.com/entry/20080522/1211469827)

仕事で引継したプログラムを見ると、`Data Source` は上記のように書かれていませんでした。

```
string conStr = "Data Source=ORCL;User id=scott;Password=tiger;";
```

ここで指定された `ORCL` では接続先が特定できません。これは「ネットサービス名」で、別途 `tnsnames.ora` ファイルに設定を記述しておいて、ライブラリが設定された内容を読みます。

参考：[tnsnames.oraと接続文字列の関係 #oracle - Qiita](https://qiita.com/smitho/items/62164fb267cddfaf1e90)

```tnsnames.ora
ORCL = (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL)))
```

これでは見づらいのでインデントします。

```tnsnames.ora
ORCL = 
    (DESCRIPTION =
        (ADDRESS_LIST =
            (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521))
        )
        (CONNECT_DATA =
            (SERVICE_NAME = ORCL)
        )
    )
```

この `tnsnames.ora` は、どこに置けばいいのでしょうか。

Oracle Client をインストールして `Net Manager` ツールを使うと、GUI で `tnsnames.ora` を所定の場所に作成してくれます。.NET アプリの Oracle Client ライブラリは、その場所に保存された `tnsnames.ora` を読んでくれるようです。
ただし、Oracle Client を複数インストールすると、どの設定が読まれるのか、にわかに分からなくなります。

参考：[【考察】Oracle Clientを何度もインストールするとどこのtnsnames.oraを使っているかわからなくなるという話 #Windows - Qiita](https://qiita.com/bearbutler/items/c42ea3c1caa521b6ce10)

`tnsnames.ora` ファイルは、.NET アプリの実行ファイルと同じフォルダに置けば、それが読込されます。

参考：[Managed な ODP.NET で tnsnames.ora を参照する (Windows, Visual Studio 2015) #oracle - Qiita](https://qiita.com/monta31538/items/b31382e98ae03f5d9c3c)