C#でMySQLからSELECTした結果を取り出したい

C#は素人なのと、あとコードは再現なので不適切なコードになっている可能性が高いです。
おそらくもっといい解法があるはずですが、調べた限りではよくわかりませんでした。


# 課題

テーブルAからSELECTする、テーブルBからSELECTする、その後ふたつのデータを色々やって最後にテーブルCにインサートする、みたいなことがやりたかったわけですよ。
コード側で色々と処理を行う必要があるため、JOINやINSERT SELECTではなく一度コード側にデータを引き取るのが前提です。


# 問題

ベストプラクティスがわからない。
適切なサンプルコードが見付からない。

信頼できるドキュメントはMicrosoft公式くらいしかないわけですが、そこに載ってるサンプルコードはどうにも役に立ちません。

https://docs.microsoft.com/ja-jp/azure/mysql/connect-csharp
https://docs.microsoft.com/ja-jp/dotnet/api/overview/azure/mysql?view=azure-dotnet

```cs:公式サンプルの例
using (MySqlConnection conn = new MySqlConnection(connectionString))
{
    conn.Open();

    // SQL発行
    MySqlCommand selectCommand = new MySqlCommand("SELECT * FROM MyTable", conn);
    MySqlDataReader results = selectCommand.ExecuteReader();

    // 行ごとにループ
    while(results.Read())
    {
        Console.WriteLine("Column 0: {0} Column 1: {1}", results[0], results[1]);
    }
}
```

ループ中でログ出力する例しか載ってない。
そんな役に立たないコードじゃなくてもっとこう、取得結果を`return`して他所で使い回す方法はないんですかね。

しかも`results[0]`とか、今どき列番号で取ってくるなんて有り得ないじゃろ。
列名で取ってきてくれよ。


# やってみた

```cs
// メイン
public static void Main{
    MySqlConnection mySqlConnection = new MySqlConnection("ConnectionString");
    mySqlConnection.Open();
    
    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public async Task<DataReader> getTable(string tableName){
    using (var command = mySqlConnection.CreateCommand()) {
        command.CommandText = $"SELECT * FROM {tableName}";
        using (var reader = command.ExecuteReaderAsync()) {
            return reader;
        }
    }
}
```

できた。

はい、これ`There is already an open DataReader associated with this Connection which must be closed first`とか言われて死にます。
どうもDataReaderは同時にひとつしか開けないらしい。
二つ目のSQLを投げる前に、一つ目のSQLはCloseしなくてはなりません。
Closeすると当然データが取れなくなるので、Closeする前に値を取り出しておかねばならないということです。


# 少し変更した

DataReaderから値を取得して、Dictionaryに突っ込んで返すようにしました。

```cs
static MySqlConnection mySqlConnection;

public static void Main{
    mySqlConnection = new MySqlConnection("ConnectionString");
    mySqlConnection.Open();

    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public Dictionary<string, Dictionary<string, string>> getTable(string tableName){
    using (var command = mySqlConnection.CreateCommand()) {
        command.CommandText = $"SELECT * FROM {tableName}";
        using (var reader = command.ExecuteReader()) {
            if (reader.HasRows) {
                while (reader.Read()) {
                    JObject line = new JObject();
                    for (int i = 0; i < reader.FieldCount; i++) {
                        line.Add(reader.GetName(i), reader.GetString(i));
                    }
                    ret.Add(reader.GetString("id"), line);
                }
            }
        }
    }
    return ret;
}
```

これでできた！
と思いきや、何故かこれでも`already an open DataReader`が発生することがあります。
なんで？returnする前にusingの外に出てるやろ？
原因がさっぱりわからなかったので諦めました。

しかも、どうやらその行の列を全て取得するみたいなメソッドがどうも存在しないっぽい。
いちいち`FieldCount`で列数を調べて全列をループで回して取り出しています。
SQL発行する度に毎回行×列のループを回さねばならないとか、本当かこれ？？？

そもそも最初から[fetchAll](https://www.php.net/manual/ja/pdostatement.fetchall.php)があれば解決なのですよ。
どうしてこの程度のメソッドが用意されてないのですかね？


# とりあえず完成

mySqlConnectionを毎回開けばええんや。

```cs
public static void Main{
    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public Dictionary<string, Dictionary<string, string>> getTable(string tableName){
    using (MySqlConnection mySqlConnection = new MySqlConnection("ConnectionString")) {
        mySqlConnection.Open();
        using (var command = mySqlConnection.CreateCommand()) {
            command.CommandText = $"SELECT * FROM {tableName}";
            using (var reader = command.ExecuteReader()) {
                if (reader.HasRows) {
                    while (reader.Read()) {
                        var line = new Dictionary<string, string>();
                        for (int i = 0; i < reader.FieldCount; i++) {
                            line.Add(reader.GetName(i), reader.GetString(i));
                        }
                        ret.Add(reader.GetString("id"), line);
                    }
                }
            }
        }
    }
    return ret;
}
```

いや……一応これで動いたんだけどさあ、どう考えてもおかしいだろ、これ。
SQLを発行するたび毎回`MySqlConnection`をnewしてるってことは、PHPで言うと毎回[new PDO](https://www.php.net/manual/ja/class.pdo.php)ってしてるってことですよね。
そんな書き方ありえなーい。
そもそも高々SQL発行するだけなのに何重に括らせる気なんだよ。

なんかもっとこう、ちゃんとした正しい書き方ってのがあるよね？
あると思うのですよ。
間違いなく存在するでしょう。


# 実はありまぁす

[DataTable](https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/dataset-datatable-dataview/datatables)ってのがあった。

```cs
public static void Main{
    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

public DataTable getTable(string tableName) {
    DataTable tbl = new DataTable();
    using(MySqlConnection mySqlConnection = new MySqlConnection(builder.ConnectionString)) {
        mySqlConnection.Open();
        using(var command = mySqlConnection.CreateCommand()) {
            command.CommandText = $"SELECT * FROM {tableName}";
            using(var reader = command.ExecuteReader()) {
                tbl.Load(reader);
            }
        }
    }
    return tbl;
}
```

これでDataTableに全データが入ってくるようで、MySqlConnectionを閉じた後でも問題なく参照することができました。

どう考えても真っ先にこれを紹介すべきなのに、普通に調べたら全然出てこないとかいう。
ここまで調べるのにめっちゃ時間かかった。

なおMySqlConnectionを閉じないとエラーになるのは相変わらずです。
どうやったら使い回せるんですかね？
それ以前に、そもそも本当にコレが最終解なんですかね？

# その他

`C#`では特に目立つ気がするのですが、Stackoverflowを機械訳したような[ゴミURLばっかり引っかかってくる](https://www.google.com/search?q=There+is+already+an+open+DataReader+associated+with+this+Connection+which+must+be+closed+first&tbs=qdr:y&lr=lang_ja)のはどうにかならないんですかね？