データベースアクセスのパフォーマンスチューニング例 | 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 ;
}
