データベースアプリケーションのテクニックの1つにアイデンティティ・マッピングがあります。これはデータベース上の行とメモリー上のオブジェクトを1対1に対応させようというものです。すべての行をメモリーに読み込むという意味ではなく、1つの行に対応するオブジェクトを複数作成しない点に主眼が置かれています。一意マッピング・恒等写像などと訳すことができますが、ここではアイデンティティ・マッピングと呼んでおきます。
1つの行に対応するオブジェクトがメモリー上に複数ある状況というのは、複数画面で同じ行を参照している場合に起こりやすいです。例えば一覧画面でロードしたいずれかの行が別画面で表示されていたりロジック実行で必要となったりといった状況です。
1つの行に対応するオブジェクトがメモリー上に複数あると、行を更新したときに同じ行に対応する別のオブジェクトも更新する必要があります。これを怠ると更新前のオブジェクトが表示されたり、更新前のオブジェクトを使ってロジックが実行されるといった不具合が発生します。アイデンティティ・マッピングではオブジェクト更新時に同じ行を表す別のオブジェクトがありませんのでこのような配慮が不要となります。
アイデンティティ・マッピングを実現するためにテーブルの主キーをキーに持ち、行に対応するオブジェクトを値に持つディクショナリーを用意することが考えられます。データベースからテーブルを読み取ったときは、このディクショナリと突き合わせ処理して、同一キーの要素があればオブジェクトのプロパティをデータベースから読み取った値に更新します。同一キーの要素がなければディクショナリーに追加します。リポジトリーにこのような仕組みを持たせておけば、すべての読み取り処理でアイデンティティ・マッピングを施すことができます。
この仕組みで問題となるのが行に対応するオブジェクトを保持するために設けたディクショナリーは要素を除外することができず、プログラム起動後に1度でも読み取った行が居座り続けるためメモリーを圧迫することです。NetFrameworkのガーベジコレクターはどこからも参照されなくなったオブジェクトを破棄対象とみなしますが、この場合ディクショナリーが参照を持っているので破棄対象になりません。
そこでこのディクショナリーの要素を弱参照するのです。 どこかの画面から参照されていれば要素は破棄されませんがどの画面からも参照されなくなりディクショナリーが弱参照するのみとなればガーベージコレクターによる破棄対象となります。
下記は要素を弱参照で持つディクショナリの例です(C# 6.0文法を使用しています)。インデクサーは読み取り専用にしています。新しい要素は内部で保持するItemCreator
によって作成します。RemoveDeadItems()
によって破棄済みの要素を除外します。このようなディクショナリーを使って読み取ったデータを保持します。
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
public class WeakReferenceDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue> where TValue : class { IDictionary<TKey, WeakReference<TValue> UnderlyingDictionary = new Dictionary<TKey, WeakReference<TValue>();
public Func<TKey, TValue> ItemCreator { get; private set; }
IEnumerable<TKey> GetDeadKeys() { foreach (var kv in UnderlyingDictionary) { TValue target; if (!kv.Value.TryGetTarget(out target)) yield return kv.Key; } }
public void RemoveDeadItems() { lock (this) { foreach (var key in GetDeadKeys().ToArray()) { UnderlyingDictionary.Remove(key); } } }
public TValue this[TKey key] { get { lock (this) { WeakReference<TValue> value; TValue target; if (UnderlyingDictionary.TryGetValue(key, out value) && value.TryGetTarget(out target)) return target; target = ItemCreator(key); UnderlyingDictionary[key] = new WeakReference<TValue>(target); return target; } } }
public int Count => UnderlyingDictionary.Count;
public IEnumerable<TKey> Keys => UnderlyingDictionary.Keys;
public IEnumerable<TValue> Values { get { foreach (var kv in UnderlyingDictionary) { TValue target; if (kv.Value.TryGetTarget(out target)) yield return target; } } }
public IEnumerator<KeyValuePair<TKey, TValue> GetEnumerator() { foreach (var kv in UnderlyingDictionary) { TValue target; if (kv.Value.TryGetTarget(out target)) yield return new KeyValuePair<TKey, TValue>(kv.Key, target); } }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
public WeakReferenceDictionary(Func<TKey, TValue> itemCreator) { ItemCreator = itemCreator; } }
|