キャッシュを作ってDataTableのアクセスを高速にする

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