データベースアクセスのパフォーマンスチューニング例 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2423
の続きを作ろうと思って「3分間クッキング」ならぬ「30分間プログラミング」になってしまいました。テンプレートを使ったものと、DataRow をそのまま使ったものとを 2 つ作ってしまったかですね。DataTable や List<> の場合には、Select メソッドを使えるのではないか?というのもありますが、Dictionary を使っているので速度的には断然高速なはずです。少なくとも、20万件ぐらいでも、Dictionary の場合は、一瞬(0.001秒以下)で返ってきます。
■利用の仕方
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 にキャッシュしているだけです。某案件では、これに似たことをやりましたが、こっちのほうが洗練されている…と思う。
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 参照)いいでしょう。
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文でいちいちチェックしなくてよいので。
string name = cache.Get( key ).name ;
敢えて空を調べたいときは、以下のように書きます。
string name = ""; if ( cache.Get(key) != cache.Empty ) { name = cache.Get( key ).name ; }