19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-02-24

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

公式サンプルの例
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]とか、今どき列番号で取ってくるなんて有り得ないじゃろ。
列名で取ってきてくれよ。

やってみた

// メイン
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に突っ込んで返すようにしました。

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があれば解決なのですよ。
どうしてこの程度のメソッドが用意されてないのですかね?

とりあえず完成

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

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ってしてるってことですよね。
そんな書き方ありえなーい。
そもそも高々SQL発行するだけなのに何重に括らせる気なんだよ。

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

実はありまぁす

DataTableってのがあった。

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ばっかり引っかかってくるのはどうにかならないんですかね?

19
13
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?