ExecuteReaderを使用したらMaxPoolSize超過した失敗事例

# はじめに
ログに実行したSQLを出力するように修正した際に、`ExecuteReader`の使用箇所を１メソッドに纏めるように修正をしたのですが、アプリケーションを使用しているうちに何も動作しなくなり、`MaxPoolSize`が超過した例外メッセージが出力されました。

# 変更内容
## 変更前
省略して書いてます。
社内のDB操作ライブラリーを使用しているので、記述が一般と若干違います。

```cs
using DbProduct manager = new DbAdapter(DBInfo).Create()
{
   // DB接続
   manager.ConnectDB(false);

   string message = "メソッド名";
   Logger.DebugSQL(message, sql, sqlParameter);
   DbDataReader reader = manager.ExecuteReader(sql, sqlParameter);
}
```

## 変更後
Readerメソッド側に接続情報を移動させました。

```cs
string message = "メソッド名";
using　(DbDataReader reader = Reader(messaage, sql, sqlParameter))
```

`DbProduct`に`using`で囲んでしまうと、呼び出し先の`reader`で`close`されているとのエラーになるので、 接続側の`using`を外しました。
これにより値は取得できていたので、よしとしていた。

```cs
static DbDataReader Reader(string messaage, string sql, params object[] sqlParameter)
{
    DbProduct manager = new DbAdapter(DBInfo).Create();
    // DB接続
    manager.ConnectDB(false);

    Logger.DebugSQL(message, sql, sqlParameter);

    return DbDataReader reader = manager.ExecuteReader(sql, sqlParameter);
}
```

# MaxPoolSize 超過エラー
ところが、アプリケーションを使用しているうちに何も動作しなくなり、`MaxPoolSize`が超過した例外メッセージが出力されました。

そこでPostgreSQLのpsqlコマンドで、2秒単位で接続数を監視できるので実行してみました。
すると、アプリケーションを動かしていくたびに、どんどん接続数が増えていきました。

```sql
SELECT numbackends FROM pg_stat_database; \watch
```

https://qiita.com/yokra9/items/f0ffafe1a05f701dc2da

# 改善内容
接続に関しては、`DbProduct` に `using` で囲む必要がありましたね。
でも、出来るだけ使用箇所を纏めたい。

主な使用用途として、存在確認とデータ1個取得です。
それ以外は、これまで通り `manager` を引数に渡してデータ取得するようにしています。

```cs:値取得
static DbDataReader Reader(DbProduct manager, string messaage, string sql, params object[] sqlParameter)
{
    DbDataReader reader;
    if (sqlParameter.Length == 0)
    {
        Logger.DebugLogSQL(message, sql);
        reader = manager.ExecuteReader(sql);
    }
    else
    {
        Logger.DebugLogSQL(message, sql, sqlParameter);
        reader = manager.ExecuteReader(sql, sqlParameter);
    }

    return reader;
}
```

```cs:レコード存在確認用
public static bool HasReader(string message, string sql, params object[] sqlParameter)
{
    bool result = false;
    using (DbProduct manager = new DbAdapter(DBInfo).Create())
    {
        // DB接続
        manager.ConnectDB(false);

        using DbDataReader reader = Reader(manager, message, sql, sqlParameter);
        result = reader.HasRows;
    }

    return result;
}

// 使用例
bool result = HasReader("ExistsPass:合格有無確認", sql, sqlParameter);
```

```cs:データ1個取得用
public static T? Scalar<T>(string message, string sql, params object[] sqlParameter)
{
    using (DbProduct manager = new DbAdapter(DBInfo).Create())
    {
        // DB接続
        manager.ConnectDB(false);

        using DbDataReader reader = Reader(manager, message, sql, sqlParameter);
        if (reader.HasRows)
        {
            reader.Read();
            Object obj = reader.GetValue(0);

            switch (obj)
            {
                case int i:
                    return (T)(object)i;
                case long l:
                    return (T)(object)l;
                case double d:
                    return (T)(object)d;
                case decimal dc:
                    return (T)(object)dc;
                case float f:
                    return (T)(object)f;
                case bool b:
                    return (T)(object)b;
                case DateTime dt:
                    return (T)(object)dt;
                case string s:
                    return (T)(object)s;
                default:
                    return (T)(object)default!;
            }
        }
    }

    return default;
}

// 使用例
int result = (int)Scalar<decimal>("GetMaxRepeatCount:繰り返し最大回数取得", sql, sqlParameter);
```

そういえば、SQLで、`nvl(MAX(pass_type), 0)`　で値を取得した際の型は、`decimal`型なんですよね。
ジェネリクスメソッドで、Scalar<int> を指定すると、型変換エラーになってしまいました。

# 最後に
`using　(DbDataReader reader = Reader(・・・` として、`using`で囲んだから、呼び出し先の接続も閉じると思ってしまったのは失敗でした。
接続は、接続で閉じないと駄目でしたね。