【.NET】Oracleクライアント不要なODP.NET Managed Driverの接続方法

# はじめに
Oracle に接続する場合、対象PCに Oracle クライアントをインストールする必要があるアプリケーションがありまして、リプレース作業に伴い「ODP.NET Managed Driver」に切り替えてOracle クライアントのインストールする手順を省きたいと思っています。
「ODP.NET Managed Driver」は、.NET 4以降のアプリケーションから使用できます。

ちなみに Java の場合、「Oracle Thin JDBC Driver」を使えば Oracle クライアントを介せずに直接 Oracle に接続することができます。ようやっと Java と同じことが出来るわけですね。

# 環境
Windows 10 Home
Visual Studio 2017 Communicate
Oracle Database 11g Express Edition (Oracle Database XE)
Oracle.ManagedDataAccess Ver 4.122.18.3

記事を書く上で自宅PCの環境となります。

# インストール
NuGetのソリューションのパッケージ管理で「ODP」で検索して、「Oracle.ManagedDataAccess」をインストールします。※2018/09/22現在のバージョンは、v18.3.0

参照に Oracle.ManagedDataAccess が追加されます。
また、App.config に自動的に設定が追加されます。

```xml:App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="oracle.manageddataaccess.client"
      type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.122.18.3, Culture=neutral, PublicKeyToken=89b483f429c47342"/>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
    </startup>
    <system.data>
        <DbProviderFactories>
            <remove invariant="Oracle.ManagedDataAccess.Client"/>
            <add name="ODP.NET, Managed Driver" invariant="Oracle.ManagedDataAccess.Client" description="Oracle Data Provider for .NET, Managed Driver"
                type="Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess, Version=4.122.18.3, Culture=neutral, PublicKeyToken=89b483f429c47342"/>
        </DbProviderFactories>
    </system.data>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <publisherPolicy apply="no"/>
                <assemblyIdentity name="Oracle.ManagedDataAccess" publicKeyToken="89b483f429c47342" culture="neutral"/>
                <bindingRedirect oldVersion="4.122.0.0 - 4.65535.65535.65535" newVersion="4.122.18.3"/>
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
    <oracle.manageddataaccess.client>
        <version number="*">
            <dataSources>
                <dataSource alias="SampleDataSource"
                    descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=ORCL))) "/>
            </dataSources>
        </version>
    </oracle.manageddataaccess.client>
</configuration>
```

# 接続方法
アプリから接続文字列で指定したデータソースの実体を解決するには、次のいずれかが必要になる。

* [Managed な ODP.NET で tnsnames.ora を参照する (Windows, Visual Studio 2015)](https://qiita.com/monta31538/items/b31382e98ae03f5d9c3c)
* [Oracle XE 11.2 にscott/tigerを作成する](https://qiita.com/rsakamot/items/3847d4725aab28dfeaf1)

1. 接続文字列のDataSourceに直接指定
2. app.configのdataSource要素にtnsnames.oraの内容をセット
3. app.configのsetting要素にtnsnames.oraの置き場所をセット
4. tnsnames.oraをexeファイルと同じフォルダに置く
5. tnsnames.oraの内容を取得して接続文字列のDataSourceに指定

## 1.接続文字列のDataSourceに直接指定
一番シンプルに、接続文字列のDataSourceに「ホストIPアドレス/SID」をセットする。

```csharp:接続方法1-1
string dataSource = "localhost/XE";
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

接続文字列のDataSourceに、Descriptorの内容をセットする。
注意として、Descriptorの内容についてすべてのキーワードがサポートされてはおらず、一般的に使用される一部のキーワードのみサポートとなっています。
[ODP.NET Managed Driver(管理対象ドライバ)についての私的まとめ](https://blog.shibata.tech/entry/2014/11/02/173039)

```csharp:接続方法1-2
string dataSource = "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE)))";
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

## 2.app.configのdataSource要素にtnsnames.oraの内容をセット

app.configのdataSource要素にtnsnames.oraの内容を指定します。

```xml:App.config
<oracle.manageddataaccess.client>
    <version number="*">
        <dataSources>
            <dataSource alias="SampleDataSource" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XE))) "/>
        </dataSources>
    </version>
</oracle.manageddataaccess.client>
```

接続文字列のDataSourceにdataSourceのエイリアス名をセットします。

```csharp:接続方法2
string dataSource = "SampleDataSource";
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

## 3.app.configのsetting要素にtnsnames.oraの置き場所をセット

app.configのsettings要素にtnsnames.oraの置き場所を指定します。
ORACLE_HOMEの場所は、コマンドプロンプト上で下記を実行すると表示されます。
※KEY_xe のところは、KEY_(SID名) となります。

```
>reg query "HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_xe"

; 出力内容
HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_xe
    VERSION    REG_SZ    11.2
    ORACLE_HOME_NAME    REG_SZ    XE
    ORACLE_HOME    REG_SZ    C:\oraclexe\app\oracle\product\11.2.0\server
```

ちなみに環境変数に「%ORACLE_HOME%」をセットしたが、変なエラーになったので環境変数はやめました。WindowsはレジストリからORACLE_HOMEを取得しているので何か影響が出てしまうのかな。

```xml:App.config
<oracle.manageddataaccess.client>
    <version number="*">
        <settings>
            <setting name="TNS_ADMIN" value="C:\oraclexe\app\oracle\product\11.2.0\server\network\ADMIN" />
        </settings>
    </version>
</oracle.manageddataaccess.client>
```

[tnsnames.ora ファイル](https://www.shift-the-oracle.com/oracle-net/tnsnames.html)

```bash:tnsname.ora
TEST =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = XE)
    )
  )
```

接続文字列のDataSourceにネットサービス名(TNS名)を指定します。

```csharp:接続方法3
string dataSource = "TEST";
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

## 4.tnsnames.oraをexeファイルと同じフォルダに置く

exeファイルの直下にtnsnames.oraファイルを置きます。

接続文字列のDataSourceにネットサービス名(TNS名)を指定します。

```csharp:接続方法4
string dataSource = "TEST";
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

## 5.tnsnames.oraの内容を取得して接続文字列のDataSourceに指定
サーバー上で動かすツール用です。

WindowsではORACLE_HOMEの場所はレジストリに記述されています。レジストリからORACLE_HOMEの値を取得してtnsname.oraファイルの場所を見つけ、Descriptorの内容を接続文字列のDataSourceに指定します。

レジストリを取得する際には注意が必要です。AnyCPUにしていても「32ビットを優先」にチェックが付いていると、32ビットのレジストリを取得してしまうので、チェックを外す必要があります。
[VisualStudioプロジェクトプロパティの「32ビットの優先」チェックボックス](https://devlights.hatenablog.com/entry/2014/04/28/091535)
![32ビットを優先.png](https://qiita-image-store.s3.amazonaws.com/0/14053/0c435112-1a0e-d6df-32a7-73c77dbad39d.png)

```csharp
private Dictionary<string, string> getDataSource()
{
    Dictionary<string, string> result = new Dictionary<string, string>();

    string baseKey = @"SOFTWARE\ORACLE";
    RegistryKey regkey = Registry.LocalMachine.OpenSubKey(baseKey, false);
    if (regkey == null) return result;

    string[] arySubKeyNames = regkey.GetSubKeyNames();

    regkey.Close();

    foreach (string subKeyName in arySubKeyNames)
    {
        if (subKeyName.IndexOf("KEY_") > -1)
        {
            RegistryKey subkey = Registry.LocalMachine.OpenSubKey(baseKey + "\\" + subKeyName, false);
            if (subkey == null) continue;

            string oraHome = (string)subkey.GetValue("ORACLE_HOME");
            subkey.Close();

            if (oraHome != null)
            {
                string path = oraHome + @"\network\admin\tnsnames.ora";
                StreamReader sr = new StreamReader(path);
                string tnsNames = sr.ReadToEnd();
                sr.Close();

                string[] lineAll = Regex.Split(tnsNames, @"^\s+[\r\n]|^[\r\n]", RegexOptions.Multiline);
                foreach (string line in lineAll)
                {
                    string str = line.Replace(Environment.NewLine, "").Trim();
                    if (!str.StartsWith("#") && str.IndexOf("=") > 0)
                    {
                        foreach (Match m in Regex.Matches(str, @"(^.*?)=.*?(\(.*\))"))
                        {
                            string section = m.Groups[1].Value.Trim().ToUpper();
                            result.Add(section, m.Groups[2].Value);
                        }
                    }
                }

                return result;
            }
        }
    }

    return result;
}
```

dic変数にはネットサービス名をキーにDescriptorの内容がセットされるので、dataSourceに指定します。

```csharp:接続方法5
Dictionary<string, string> dic = getDataSource();
string dataSource = dic["TEST"];
string connectString = "user id=scott;password=tiger;data source=" + dataSource;
using (OracleConnection conn = new OracleConnection(connectString)) {
    try {
        conn.Open();
        MessageBox.Show("データベースに接続しました。");
    }
    catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}
```

# 最後に
もう2018年9月です、Windows 10は.NET Framework 4.6が標準に搭載されています。
Windows 7の延長サポート終了日は2020年1月14日となります。

古い環境はリプレースしていきましょうよ。