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

データベースアクセスのパフォーマンスチューニング例 | 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> と同じ仕組みですね。

カテゴリー: C# パーマリンク