55080


●概要  
以下のようなプログラムを作成しています。  
・MySQLからレコードを取得（MySqlConnector/NET 8.16を使用）  
・取得したレコードを1行ずつCSVに出力  
・DBアクセス部分とCSV出力部分はクラスで分けるなどして分離させた実装とする。  
・DBアクセスメソッド内でCSV出力メソッドを呼び出すのはNG

以下①②③の背景があり現在は③の実装をしています。  
もっと良い手法は無いのなという思いがあり、質問させていただきました。  
独学のため先人のベストプラクティスを見る機会などがあまりなく、何かご教示いただけると幸いです。

●実装背景  
①  
私は最初、DBアクセスとして下記①のようなDataTableを戻り値とするメソッドを実装をしました。

②  
しかし、今回の目的ではDataTableで全てのレコードをメモリに展開する必要は無いと思い、下記②のようなMySqlDataReaderを戻り値とするメソッドに変更しました。  
ところが②のコードで戻り値として取得したMySqlDataReaderからデータを読み出そうとすると実行時エラーになりました。  
原因としては、データを読み出す時点でMySqlDataReaderを生成したMySqlConnectionとMySqlCommandが既にDisposeされているからでした。

③  
現在、下記③の実装になりました。  
クラス変数としてconnectionとcommandを持つ形になったのですが、この実装でいくつか残念に思っている点があります。  
１．using句での自動Disposeではなく、呼び出し側で明示的にDisposeメソッドを呼ばないといけない点  
２．DataAccessクラスに他のデータ取得メソッドが追加された場合、SqlDataConnectionやSqlDataCommandの競合が起きる可能性のある点。それを避けるためにはDataAccessクラスを複数インスタンス化しないといけない点。

他のDBMSでもADO.NETデータプロバイダであれば同じような実装だと認識しているので、他のDBMSでの知見でも構わないです。  
よろしくお願いします。

①

[code]

    class DataAccess
    {
        public DataTable GetXyzData()
        {
            MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder();
            builder.Server = "aaa";
            builder.Database = "bbb";
            builder.UserID = "ccc";
            builder.Password = "ddd";
    
            using (MySqlConnection conn = new MySqlConnection(builder.ConnectionString))
            {
                StringBuilder sql = new StringBuilder();
                sql.AppendLine("SELECT");
                sql.AppendLine("  COL1");
                sql.AppendLine("  , COL2");
                sql.AppendLine("  , COL3");
                sql.AppendLine("FROM");
                sql.AppendLine("  TABLE_XYZ");
    
                using (MySqlCommand cmd = new MySqlCommand(sql.ToString(), conn))
                {
                    MySqlDataAdapter adp = new MySqlDataAdapter(cmd);
                    DataTable tbl = new DataTable();
                    adp.Fill(tbl);
    
                    return tbl;
                }
            }
        }
    }
    
[/code]

②

[code]

    class DataAccess
    {
        public MySqlDataReader GetXyzData()
        {
            MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder();
            builder.Server = "aaa";
            builder.Database = "bbb";
            builder.UserID = "ccc";
            builder.Password = "ddd";
    
            using (MySqlConnection conn = new MySqlConnection(builder.ConnectionString))
            {
                StringBuilder sql = new StringBuilder();
                sql.AppendLine("SELECT");
                sql.AppendLine("  COL1");
                sql.AppendLine("  , COL2");
                sql.AppendLine("  , COL3");
                sql.AppendLine("FROM");
                sql.AppendLine("  TABLE_XYZ");
    
                using (MySqlCommand cmd = new MySqlCommand(sql.ToString(), conn))
                {
                    MySqlDataReader dataReader = cmd.ExecuteReader();
                    return dataReader;
                }
            }
        }
    }
    
[/code]

③

[code]

    class DataAccess
    {
        private MySqlConnection conn;
        private MySqlCommand cmd;
    
        public void GetConnection()
        {
            MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder();
            builder.Server = "aaa";
            builder.Database = "bbb";
            builder.UserID = "ccc";
            builder.Password = "ddd";
    
            this.conn = new MySqlConnection(builder.ConnectionString);
        }
    
        public void DisposeConnection()
        {
            this.conn.Dispose();
        }
    
        private void GetCommand(string sql)
        {
            this.cmd = new MySqlCommand(sql, this.conn);
        }
    
        public void DisposeCommand()
        {
            this.cmd.Dispose();
        }
    
        public MySqlDataReader GetXyzData()
        {
            StringBuilder sql = new StringBuilder();
            sql.AppendLine("SELECT");
            sql.AppendLine("  COL1");
            sql.AppendLine("  , COL2");
            sql.AppendLine("  , COL3");
            sql.AppendLine("FROM");
            sql.AppendLine("  TABLE_XYZ");
    
            GetCommand(sql.ToString());
    
            MySqlDataReader dataReader = this.cmd.ExecuteReader();
            return dataReader;         
        }
    }
    
[/code]


CC BY-SA 4.0
2019-05-18T22:51:10.273
55075
2019-05-19T01:59:26.507
2019-05-19T01:14:54.903
34376
34376
question
c# mysql .net
C#でDataReaderを戻り値とするメソッドの実装について相談


まず、[一般的な名前付け規則](https://docs.microsoft.com/ja-jp/dotnet/standard/design-
guidelines/general-naming-conventions#using-abbreviations-and-
acronyms)として`conn`や`cmd`のような略称を使うべきではありません。`connection`、`command`などを検討ください。

`MySqlConnection`はDBとの接続を持つため保持する必要がありますが、`MySqlCommand`（というよりその親クラス[`DbCommand`](https://docs.microsoft.com/ja-
jp/dotnet/api/system.data.common.dbcommand?view=netframework-4.8)）はいつ解放しても特に支障がありません。従って`GetXyzData`内で`using`を使ってしまう方がいいでしょう。

`MySqlConnection`の解放タイミングについてお悩みのようですが、そうであれば`IDisposable`を実装してしまうか、ガベージコレクタ任せで解放しないという選択肢があります。

以上を踏まえて

[code]

    class DataAccess : IDisposable {
        readonly MySqlConnection connection = new MySqlConnection(new MySqlConnectionStringBuilder {
            Server = "aaa",
            Database = "bbb",
            UserID = "ccc",
            Password = "ddd",
        }.ConnectionString);
    
        public void Dispose() => connection.Dispose();
    
        public MySqlDataReader GetXyzData() {
            var sql = "SELECT COL1, COL2, COL3 FROM TABLE_XYZ";
            using (var command = new MySqlCommand(sql, connection))
                return command.ExecuteReader();
        }
    }
    
[/code]

* * *

>
> DataAccessクラスに他のデータ取得メソッドが追加された場合、SqlDataConnectionやSqlDataCommandの競合が起きる可能性のある点。それを避けるためにはDataAccessクラスを複数インスタンス化しないといけない点。

SQL Serverの場合、[MARS; 複数のアクティブな結果セット](https://docs.microsoft.com/ja-
jp/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-
sets)機能があるため、１つの接続内で複数のコマンドを実行することができます。しかし、MySQLにはこの機能がないため、先行する`MySqlDataReader`が完了してから次のコマンドを実行するか、コマンド毎に接続を行うかのどちらかであり、どちらを採用するかは設計の問題となります。


CC BY-SA 4.0
2019-05-19T01:59:26.507
55080
2019-05-19T01:59:26.507
4236
55075
answer