データベースアクセスのパフォーマンスチューニング例 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2423
キャッシュを作ってDataTableのアクセスを高速にする | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2780
の続きで、複数キー対応のDictonaryを作成してキャッシュを有効に使う例をば。
つまりは、「Dictonary」のような形で、2つの int型のキーを持っていて、それから ProductRow オブジェクト(Productテーブルの列オブジェクト)を取ってくる。
■利用方法
まずは、使い方を。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /// <summary> /// Product のキャッシュ /// </summary> private ProductCache pcache = null ; /// <summary> /// 複数キーのキャッシュ実行 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click( object sender, EventArgs e) { if (cache == null ) { SqlConnection cn = new SqlConnection(CNSTR); SqlDataAdapter da = new SqlDataAdapter( "SELECT * FROM PRODUCT" , cn); DataTable dt = new DataTable(); da.Fill(dt); // キャッシュに設定 Product product = new Product(); DataBind.Conv(dt, product); this .pcache.Fill(product.Rows); } int id = int .Parse(textBox1.Text); int subid = int .Parse(textBox2.Text); // 2つのキーで検索 ProductRow row = this .pcache[id, subid]; textBox2.Text = row.name; } |
初回だけデータベースに接続して、キャッシュを作成します。
DataRow から独自の Product テーブルクラスにコピーした後に、キャッシュ pcache に保存します。
キャッシュへのアクセスは、pcache[ id, subid ] のように配列でアクセスできて、戻り値は ProductRow オブジェクトという具合。DataRow よりも型付の ProductRow のほうが、row.id とか row.name とかのプロパティアクセスができるので、コードの品質はあがるはずです。インテリセンスが使ええるし、コンパイル時に列名が間違っているとエラーになるし。
■内部実装
キャッシュクラスはこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | /// <summary> /// 複数キーに対応したDictonary /// キーが2つの場合 /// </summary> /// <typeparam name="K1"></typeparam> /// <typeparam name="K2"></typeparam> /// <typeparam name="V"></typeparam> public abstract class Dictonary<K1, K2, V> : Dictionary< string , V> where V : new () { public V Empty { get ; set ; } /// <summary> /// コンストラクタ /// </summary> public Dictonary() { this .Empty = new V(); } // protected override protected void Add( string key) { } protected new V this [ string key] { get { return Empty; } set { } } protected new bool ContainsKey( string key) { return false ; } /// <summary> /// キーと値を追加 /// </summary> /// <param name="key1"></param> /// <param name="key2"></param> /// <param name="value"></param> public void Add(K1 key1, K2 key2, V value) { this .Add(MakeKey(key1, key2), value); } /// <summary> /// キーを作成 /// </summary> /// <param name="key1"></param> /// <param name="key2"></param> /// <returns></returns> public string MakeKey(K1 key1, K2 key2) { return key1.ToString() + "_" + key2.ToString(); } public abstract string MakeKey(V value); /// <summary> /// []演算子でアクセス /// </summary> /// <param name="key1"></param> /// <param name="key2"></param> /// <returns></returns> public V this [K1 key1, K2 key2] { get { string key = MakeKey(key1, key2); return this .ContainsKey(key) ? this [key] : Empty; } set { string key = MakeKey(key1, key2); if ( this .ContainsKey(key)) { this [key] = value; } else { this .Add(key, value); } } } /// <summary> /// 指定したキーを含むか /// </summary> /// <param name="key1"></param> /// <param name="key2"></param> /// <returns></returns> public bool ContainsKey(K1 key1, K2 key2) { string key = MakeKey(key1, key2); return base .ContainsKey(key); } /// <summary> /// データを設定 /// </summary> /// <param name="table"></param> public void Fill(IList<V> items) { foreach (V it in items) { this [MakeKey(it)] = it; } } } |
抽象クラスになっているのは、DataRow にあたる V から主キーは直接取り出せないので、一度継承するという方式です。データエンティティに「主キーの属性」みたいなのをつければ、ここは解決するかも。
もうひとつは、DataRow から型付のDataRowクラスにコンバートするためのクラスが必要と思います。毎回コンバートだけコードを書くのは面倒なので、テンプレートとリフレクションを使って自動化します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | /// <summary> /// リフレクションを簡単にするための準備 /// </summary> public interface IDataTable { Type GetRowType(); object CreateRow(); } /// <summary> /// 型付DataTable用のテンプレート /// </summary> /// <typeparam name="DRType"></typeparam> public class DTTemplate<DRType> : IDataTable, IListSource { /// <summary> /// DataRow の型を返す /// </summary> /// <returns></returns> public Type GetRowType() { return typeof (DRType); } /// <summary> /// DataRow のリスト /// </summary> protected List<DRType> _rows = new List<DRType>(); public List<DRType> Rows { get { return _rows; } } /// <summary> /// 新しい DataRow を作成 /// </summary> /// <returns></returns> public DRType NewRow() { return (DRType)Activator.CreateInstance( typeof (DRType)); } /// <summary> /// 新しい DataRow を作成(object型) /// </summary> /// <returns></returns> public object CreateRow() { return NewRow(); } // インターフェースの実装 /// <summary> /// 内部リストから IList を使う /// </summary> public bool ContainsListCollection { get { return true ; } } /// <summary> /// IListのコレクションを返す /// </summary> /// <returns></returns> public System.Collections.IList GetList() { return this .Rows; } } /// <summary> /// <summary> /// 形無しDataSetを型付DataSetにコンバートするクラス /// </summary> public class DataBind { public static void Conv(DataTable src, IDataTable dest) { Type rowType = dest.GetRowType(); System.Collections.IList rows = ((IListSource)dest).GetList(); foreach (DataRow row in src.Rows) { object item = dest.CreateRow(); // リフレクションを使って、列ごとにコピーする foreach (DataColumn column in src.Columns) { // この部分はキャッシュすると高速化する string key = column.ColumnName; PropertyInfo pi = rowType.GetProperty(key); if (pi != null ) { // object型 -> 元の型のキャストでエラーになるため、 // 一度元の型に ChangeType してから代入する。 pi.SetValue(item, Convert.ChangeType(row[key], pi.PropertyType), null ); } } rows.Add(item); } } } |
これを作成しておいて、自前のエンティティのクラスやキャッシュクラスを作成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /// <summary> /// 複数キーを持つ Product エンティティクラス /// </summary> public class ProductRow { public int id { get ; set ; } // PK public int subid { get ; set ; } // PK public string name { get ; set ; } public int parent_id { get ; set ; } public string desc { get ; set ; } public DateTime updatedate { get ; set ; } } /// <summary> /// 型付DataTable /// </summary> public class Product : DTTemplate<ProductRow> { } /// <summary> /// Product キャッシュクラスを作成 /// </summary> public class ProductCache : Dictonary< int , int , ProductRow> { /// <summary> /// ProductRow からキー文字列を取得 /// </summary> /// <param name="value"></param> /// <returns></returns> public override string MakeKey(ProductRow value) { return MakeKey(value.id, value.subid); } } |
テンプレートのまま使ってもいいけど、もう一度のエンティティクラスの名前を付け直すとコーディングが楽です。テーブル名と揃えておくと、データベース仕様書とのマッチングが楽とか。
ちなみに、キーが3つある場合は、先の Dictonary のコードをコピーして、
1 | public abstract class Dictonary<K1, K2, K3, V> : Dictionary< string , V> where V : new () |
のように倍増させておきます。あらかじめ、6個ぐらい作っておけば大抵のテーブルは大丈夫かと。デリゲートの <Action> と同じ仕組みですね。