エンティティフレームワークを介して駆動される非常にカスタムデータであるASP.NET/Umbracoを利用したWebサイトを構築しています。これは、ビジーなサイトであるため、大量のデータクエリ(たとえば、キーワードによる検索)をキャッシュする必要があります。
しかし、ユーザーが新しいデータエントリを作成するとき、キャッシュされたクエリ(検索など)をすべてクリアして、新しいエントリが結果で利用できるようにする必要があります。
したがって、私の作成、削除、更新メソッドでは、次のメソッドを呼び出しています。
public static void ClearCacheItems()
{
var enumerator = HttpContext.Current.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
HttpContext.Current.Cache.Remove(enumerator.Key.ToString());
}
}
これは本当に悪いのですか?キャッシュされたアイテムをクリアする他の方法がわかりませんか?
使用する方法は、実際にはキャッシュをクリアする正しい方法です。コードにわずかな「エラー」が1つだけあります。列挙子のみ有効元のコレクション変更されないままである限り。そのため、ほとんどの場合コードは機能しますが、特定の状況では小さなエラーが発生する可能性があります。基本的に同じことを行いますが、列挙子を直接使用しない次のコードを使用するのが最善です。
List<string> keys = new List<string>();
IDictionaryEnumerator enumerator = Cache.GetEnumerator();
while (enumerator.MoveNext())
keys.Add(enumerator.Key.ToString());
for (int i = 0; i < keys.Count; i++)
Cache.Remove(keys[i]);
特定の機能ドメイン1つだけのASP.NETキャッシュ全体をクリアすることは、少しやり過ぎに思われます。
中間オブジェクトを作成し、キャッシュされたすべてのクエリをそこに格納できます。このオブジェクトは、辞書オブジェクトの単なるラッパーである可能性があります。すべてのクエリは、ASP.NETキャッシュを直接操作するのではなく、このオブジェクトを使用する必要があります。
次に、このオブジェクトを必要なときにASP.NETキャッシュに追加します。クエリをクリアする必要がある場合は、このオブジェクトにアクセスして、基になる辞書をクリアします。次に、サンプルのインプリメンテーションを示します。
public sealed class IntermediateCache<T>
{
private Dictionary<string, T> _dictionary = new Dictionary<string, T>();
private IntermediateCache()
{
}
public static IntermediateCache<T> Current
{
get
{
string key = "IntermediateCache|" + typeof(T).FullName;
IntermediateCache<T> current = HttpContext.Current.Cache[key] as IntermediateCache<T>;
if (current == null)
{
current = new IntermediateCache<T>();
HttpContext.Current.Cache[key] = current;
}
return current;
}
}
public T Get(string key, T defaultValue)
{
if (key == null)
throw new ArgumentNullException("key");
T value;
if (_dictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
public void Set(string key, T value)
{
if (key == null)
throw new ArgumentNullException("key");
_dictionary[key] = value;
}
public void Clear()
{
_dictionary.Clear();
}
}
私のクエリが次のように表されている場合:
public class MyQueryObject
{
....
}
次に、このような「リージョン」キャッシュを使用します。
// put something in this intermediate cache
IntermediateCache<MyQueryObject>.Current.Set("myKey", myObj);
// clear this cache
IntermediateCache<MyQueryObject>.Current.Clear();
Cache
クラスの設計者がClear
メソッドを追加するのは非常に簡単でした。しかし、そうではなく、それは設計によるものでした-したがって、コードはbadです。
1つの問題は、コレクションが変更された場合コレクションを列挙することによるスレッドへの影響です。エラーが発生します。
キャッシュ全体をクリアする必要はありません。サーバーがメモリを必要とする場合、それはそれをクリアします。キャッシュアクセスはキーによる(理由による)なので、アクセスしようとしているものを知る必要があります。したがって、アイテムを削除する必要がある場合は、キーを使用してください。
私のアドバイスは、キャッシュのクリアが非常に簡単になるような方法でキャッシュを設計することです。たとえば、キャッシュアイテムをグループ化し(関連するキャッシュ結果を保持するクラスを作成)、IDをキーとして使用します。そのIDに関連する何かが変更されるたびに、そのIDのキャッシュをクリアします。 簡単、簡単。
はい、キャッシュをループしてアイテムを削除しないでください。それはあなたに痛みの世界を与えます(私は最近これを経験しました)。 「コレクションが変更されたため、列挙が実行されない可能性があります」というエラーが発生します。
また、検索用のキャッシュがあり、有効期限を設定していません-必要に応じて手動で無効にします。
トリックは、データが「バケット」に保持されることです。その「バケット」は通常、親識別子、またはデータベースの用語を使いたい場合は外部キーです。
したがって、特定の「顧客」のすべての「注文」をクリアする必要がある場合、キャッシュキーはCustomerIdである必要があります。私の場合、ReadOnlyCollection<T>
。だから私はループする必要はなく、単にそれを削除してから、新しいコピーを追加します。
また、二重に安全にするためにReaderWriterLockSlimを使用してキャッシュを無効にします。無効にする必要があるとわかったときは、まずデータベースを呼び出し、書き込みロックを取得し、関連するキャッシュをフラッシュして更新してから、書き込みロックを閉じます。
そうすれば、すべてシームレスです。
Cache Dependencyの使用を検討しましたか? これはMSDNの説明です とそこからのヒント:
ASP.NETアプリケーションのCacheオブジェクトに格納されているアイテムと、ファイル、キャッシュキー、いずれかの配列、または別のCacheDependencyオブジェクトとの依存関係を確立します。 CacheDependencyクラスは依存関係を監視し、それらのいずれかが変更されると、キャッシュされたアイテムが自動的に削除されるようにします。
// Insert the cache item.
CacheDependency dep = new CacheDependency(fileName, dt);
cache.Insert("key", "value", dep);
// Check whether CacheDependency.HasChanged is true.
if (dep.HasChanged)
Response.Write("<p>The dependency has changed.");
else Response.Write("<p>The dependency has not changed.");
そして この非常に熱狂的な研究者 はそれについてさらに説明しています。