データベースアクセスのパフォーマンスチューニング例 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2423
の続きを作ろうと思って「3分間クッキング」ならぬ「30分間プログラミング」になってしまいました。テンプレートを使ったものと、DataRow をそのまま使ったものとを 2 つ作ってしまったかですね。DataTable や List<> の場合には、Select メソッドを使えるのではないか?というのもありますが、Dictionary を使っているので速度的には断然高速なはずです。少なくとも、20万件ぐらいでも、Dictionary の場合は、一瞬(0.001秒以下)で返ってきます。
■利用の仕方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private string CNSTR = "" ; private AnkenCache cache = null ; private void button1_Click( object sender, EventArgs e) { if (cache == null ) { SqlConnection cn = new SqlConnection(CNSTR); SqlDataAdapter da = new SqlDataAdapter( "SELECT * FROM ANKEN" , cn); DataTable dt = new DataTable(); da.Fill(dt); // キャッシュに設定 this .cache.SetData(dt); } int id = int .Parse(textBox1.Text); DataRow row = cache.Get(id.ToString()); textBox2.Text = ( string )row[ "name" ]; } |
こんな風にキャッシュ利用します。1度目はデータベースから検索するけど、2度目以降はキャッシュを使うってことで。
キー情報は、string 型なので、適当な MakeKey なる関数を作っておくと便利です。
■内部コード
DataTable を渡して DataRow の中身を Dictionary にキャッシュしているだけです。某案件では、これに似たことをやりましたが、こっちのほうが洗練されている…と思う。
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 | public abstract class SelectCacheTableRow { protected Dictionary< string , DataRow> _dic; public DataRow Empty { get ; set ; } public SelectCacheTableRow() { _dic = new Dictionary< string , DataRow>(); this .Empty = new DataTable().NewRow(); // 空を設定 } public void Set( string key, DataRow value) { if (_dic.ContainsKey(key) == true ) { _dic[key] = value; } else { _dic.Add(key, value); } } public DataRow Get( string key) { if (_dic.ContainsKey(key) == true ) { return _dic[key]; } else { return this .Empty; } } /// <summary> /// キー情報を作成 /// </summary> /// <param name="t"></param> /// <returns></returns> public abstract string MakeKey(DataRow it); /// <summary> /// データを設定 /// </summary> /// <param name="table"></param> public void SetData(DataTable table) { foreach (DataRow it in table.Rows) { this .Set(MakeKey(it), it); } } } public class AnkenCache : SelectCacheTableRow { /// <summary> /// キー情報を作成 /// </summary> /// <param name="t"></param> /// <returns></returns> public override string MakeKey(DataRow row) { return row[ "id" ].ToString(); } } |
■テンプレートを使う
上記の例だと、DataRow しか扱えないので、単純なデータクラスを使いたい場合(こっちのほうがお勧めなんだが)、テンプレート(正確にはジェネリック)版を使います。「Project」というプロパティだけのクラスを作っておいて、プロパティ名でアクセスできるようにします。DataRow からデータクラスへのコンバートは、リフレクションを使ったバージョンを用意しておくと(型なしDataTableから型付きDataTableにコピーする方法 | Moonmile Solutions Blog 参照)いいでしょう。
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 | public abstract class SelectCache<T> where T: new () { protected Dictionary< string , T> _dic; public T Empty { get ; set ; } public SelectCache() { _dic = new Dictionary< string , T>(); this .Empty = new T(); // 空を設定 } public void Set( string key, T value) { if (_dic.ContainsKey(key) == true ) { _dic[key] = value; } else { _dic.Add(key, value); } } public T Get( string key) { if (_dic.ContainsKey(key) == true ) { return _dic[key]; } else { return this .Empty; } } /// <summary> /// キー情報を作成 /// </summary> /// <param name="t"></param> /// <returns></returns> public abstract string MakeKey(T it); /// <summary> /// データを設定 /// </summary> /// <param name="table"></param> public void SetData(List<T> items) { foreach (T it in items) { this .Set(MakeKey(it) , it); } } } public class Project { public int id { get ; set ; } public int projnum { get ; set ; } public string name { get ; set ; } public int parent_id { get ; set ; } public string desc { get ; set ; } public DateTime updatedate { get ; set ; } } public class ProjectCache : SelectCache<Project> { // キー情報を作成 public override string MakeKey(Project it) { return it.id.ToString() + "_" + it.projnum.ToString(); } } |
Empty プロパティを null にしないのは、以下のような書き方でもエラーにならないようにするためです。DataRow の場合は名前がマッチングしないと例外を発生しますが、こちらの場合は1行で書けるから便利かと。if文でいちいちチェックしなくてよいので。
1 | string name = cache.Get( key ).name ; |
敢えて空を調べたいときは、以下のように書きます。
1 2 3 4 | string name = "" ; if ( cache.Get(key) != cache.Empty ) { name = cache.Get( key ).name ; } |