こんにちは。
とりあえず作ってみる。
只野です。
業務系プログラムだとDB検索結果をプロパティクラスに設定するとか良くありますね。
そんな時にこんなコード書くじゃないですか
//SQLコマンド発行 using (SqlCommand com = con.CreateCommand()) { StringBuilder sql = new StringBuilder(); sql.AppendLine("SELECT"); sql.AppendLine("ITEMKEY AS ItemKey"); sql.AppendLine(",ITEMSTRING AS ItemString"); sql.AppendLine(",ITEMNUMERIC AS ItemNumeric"); sql.AppendLine(",ITEMDATE AS ItemDate"); sql.AppendLine("FROM"); sql.AppendLine("Table1"); com.CommandText = sql.ToString(); List<TestClass> rets = new List<TestClass>(); //SQLコマンド実行 using (DbDataReader reader = com.ExecuteReader()) { //取得結果ループ while (reader.Read()) { TestClass testclass = new TestClass(); testclass.ItemKey = Convert.ToString(reader["ItemKey"]); testclass.ItemNumelic = Convert.ToDecimal(reader["ItemNumeric"]); //……以下設定処理 } } }
私は思うわけだ
めんどくさい!
DB取得結果項目、プロパティ数が増えれば増えるほど記載するコード量が多くなります。取得結果の設定忘れや型変換ミスで良くバグが発生するポイントでもあります。
「あ~、DB取得結果をプロパティクラスにマッピングしてくれるメソッドはないものか…」
あればどんなに良いものかと思う私です。調べれば沢山出てきそうですが、こんな時は作ってみたくなる私です。
はい。
本日はC#でDB取得結果とプロパティクラスをマッピングするメソッドを作成していきます。
DB取得結果とプロパティクラスをマッピングする。
ざっとこんな仕様で作成します。
メソッド仕様
・DB取得項目名とプロパティ名でマッピングする。
・DB取得項目の型変換はプロパティの型で判断して行う。
・ジェネリックメソッドで作成する。
これができれば実装時注意する箇所が減る(はず)です。
ジェネリックメソッドの説明
実装前に“ジェネリックメソッド”の説明です。
ジェネリックメソッドは“引数の型”や”戻り値の型”はメソッド使用者が設定して良いよってメソッドになります。
通常メソッドを作成する場合は下記の様に引数、戻り値の型をメソッド作成者が指定します。
private List<int> Sample(int maxcnt) { List<int> ret = new List<int>(); for(int cnt = 0; cnt < maxcnt; cnr++) { ret.add(cnt); } return ret; }
例のメソッドは引数で渡された回数分リストを作成して戻すメソッドになります。使用者はメソッドで指定された引数、戻り値の型を用意して使用します。限定的な使用の場合は問題ないと思いますが、“処理が一緒で戻り値、引数が違う”なんてメソッドを作りたい時は上記方法ですと引数、戻り値の数分メソッドを用意しなければなりません。効率も良くないですし、仕様変更が発生した場合など考えると現実的ではありません。
そんな時に使用するのが“ジェネリックメソッド”です。
上記メソッドをジェネリックメソッドにするとこうなります。
private List<T> Sample<T>(int maxcnt) where T : struct { List<T> ret = new List<T>(); for (int cnt = 0; cnt < maxcnt; cnt++ ) { ret.Add((T)Convert.ChangeType(cnt, typeof(T))); } return ret; }
肝は“<T>”です。これをメソッド名の後ろにつけるとジェネリックメソッドで作成できます。今回例にしているメソッドの場合は、戻り値の型は使用者が選択して良いよの状態です。
ただどんな型でも選択されると不都合が起きますね。処理によっては対応しきれない場合も当然あります。そこで登場するのが“制約”です。ハンターハンターの念みたいでかっこよいですね←
はい。
制約を付ける事で使用者が選択できる型の種類を限定する事ができます。「where 型引数名 : 制約」になっています。今回は値型を指定できる制約を付けています。
今回例にしたジェネリックメソッドはこのように使用します。
List<decimal> ret1 = Sample<decimal>(10); List<long> ret2 = Sample<long>(10); List<float> ret3 = Sample<float>(10);
メソッド名<型>で使用します。これで使用者が型指定を行えるメソッドの完成です。ざっとした説明ですがジェネリックメソッドについては以上です。更に詳しく知りたい場合はグーグル先生に聞いてください←
実装
ジェネリックメソッドを使用して
class Program { //DB取得結果格納用(取得するDB項目型と合わせる) public class TestClass { public string ItemKey { get; set; } public string ItemString { get; set; } public decimal? ItemNumeric { get; set; } public DateTime? ItemDate { get; set; } public TestClass() { } } static void Main(string[] args) { //DB接続 SqlConnection con = new SqlConnection(GetConnectionString()); try { //DB接続開始 con.Open(); //SQLコマンド発行 using (SqlCommand com = con.CreateCommand()) { StringBuilder sql = new StringBuilder(); //取得項目名は設定先のクラスプロパティ名へ合わせる sql.AppendLine("SELECT"); sql.AppendLine("ITEMKEY AS ItemKey"); sql.AppendLine(",ITEMSTRING AS ItemString"); sql.AppendLine(",ITEMNUMERIC AS ItemNumeric"); sql.AppendLine(",ITEMDATE AS ItemDate"); sql.AppendLine("FROM"); sql.AppendLine("Table1"); com.CommandText = sql.ToString(); //SQL取得結果指定クラス格納 List<TestClass> rets = DbToPropertys<TestClass>(com); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { //DB接続終了 if (con != null) { con.Close(); } } } static string GetConnectionString() { //接続文字列生成 SqlConnectionStringBuilder ret = new SqlConnectionStringBuilder() { //DB接続先 DataSource = "localhost", //対象DB InitialCatalog = "TestDb", //認証方法 IntegratedSecurity = false, //ユーザー UserID = "ユーザー1", //パスワード Password = "パスワード" }; return ret.ToString(); } static List<T> DbToPropertys<T>(SqlCommand com) where T : class ,new() { List<T> ret = new List<T>(); //指定クラス内プロパティリスト取得 List<PropertyInfo> propertys = typeof(T).GetProperties().ToList(); //SQLコマンド実行 using (DbDataReader reader = com.ExecuteReader()) { //取得結果ループ while (reader.Read()) { //指定クラスインスタンス作成 T t = new T(); //取得行列数取得 int end = reader.FieldCount; //列数分ループ(ここで取得結果を対応するプロパティに設定) for (int cnt = 0; cnt < end; cnt++) { //nullの場合次の列へ if (reader.IsDBNull(cnt)) { continue; } //取得結果項目名から設定先プロパティ取得(大文字小文字の区別なし) PropertyInfo property = propertys.Find(x => x.Name.ToUpper().Equals(reader.GetName(cnt).ToUpper())); //設定先がない場合次の列へ if (property == null) { continue; } //設定先プロパティの型を取得(null許容型対応) Type converttype = property.PropertyType.IsGenericType ? Nullable.GetUnderlyingType(property.PropertyType) : property.PropertyType; //設定先プロパティへ取得結果設定 property.SetValue(t, Convert.ChangeType(reader.GetValue(cnt), converttype)); } ret.Add(t); } } return ret; } }
完成です。上記は一連の流れを記載したサンプルを含んでいます。肝のメソッドは84行目で記載しているコードです。このメソッドが今回作成したかったDB取得結果をプロパティクラスへマッピングしてくれるメソッドとなります。
マッピング用ジェネリックメソッド
static List<T> DbToPropertys<T>(SqlCommand com) where T : class ,new() { List<T> ret = new List<T>(); //指定クラス内プロパティリスト取得 List<PropertyInfo> propertys = typeof(T).GetProperties().ToList(); //SQLコマンド実行 using (DbDataReader reader = com.ExecuteReader()) { //取得結果ループ while (reader.Read()) { //指定クラスインスタンス作成 T t = new T(); //取得行列数取得 int end = reader.FieldCount; //列数分ループ(ここで取得結果を対応するプロパティに設定) for (int cnt = 0; cnt < end; cnt++) { //nullの場合次の列へ if (reader.IsDBNull(cnt)) { continue; } //取得結果項目名から設定先プロパティ取得(大文字小文字の区別なし) PropertyInfo property = propertys.Find(x => x.Name.ToUpper().Equals(reader.GetName(cnt).ToUpper())); //設定先がない場合次の列へ if (property == null) { continue; } //設定先プロパティの型を取得(null許容型対応) Type converttype = property.PropertyType.IsGenericType ? Nullable.GetUnderlyingType(property.PropertyType) : property.PropertyType; //設定先プロパティへ取得結果設定 property.SetValue(t, Convert.ChangeType(reader.GetValue(cnt), converttype)); } ret.Add(t); } } return ret; }
ここで今回はC#メソッドのSqlCommandで検索SQLを実効しDbDataReaderへ格納後、戻り値に設定するプロパティクラスリストの型でマッピングするようにしています。
使い方は検索SQL発行時の取得項目名が、引数で指定されているクラス内のプロパティ名と一致するようにするだけです。プロパティクラスの各プロパティ型はDBテーブル項目に合わせます。
これでDB検索時のめんどくさいプロパティクラスへの設定が省かれました。ただレスポンス的にどうなんだ?という問題はありますね。場合によっては使いどころを考えなくてはならないかもしれません。この辺はそのうち調べます←
本日はこの辺で