MVCアプリケーションでのページキャッシュと部分ページキャッシュに関する多くの情報を読みました。ただし、データをキャッシュする方法を知りたいです。
私のシナリオでは、LINQ to Entities(エンティティフレームワーク)を使用します。 GetNames(またはメソッドが何であれ)の最初の呼び出しで、データベースからデータを取得します。結果をキャッシュに保存し、キャッシュされたバージョンが存在する場合はそれを使用する2回目の呼び出しで保存します。
誰がこれがどのように機能するか、どこでこれを実装すべきか(モデル?)、そしてそれが機能するかどうかの例を示すことができます。
これは、通常非常に静的なデータに対して、従来のASP.NETアプリで行われているのを見てきました。
モデルでSystem.Web dllを参照し、System.Web.Caching.Cacheを使用します
public string[] GetNames()
{
string[] names = Cache["names"] as string[];
if(names == null) //not in cache
{
names = DB.GetNames();
Cache["names"] = names;
}
return names;
}
少し簡略化されていますが、それでうまくいくと思います。これはMVC固有ではなく、データのキャッシュに常にこの方法を使用しています。
以下は、私が使用する素敵でシンプルなキャッシュヘルパークラス/サービスです。
using System.Runtime.Caching;
public class InMemoryCache: ICacheService
{
public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
{
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
}
return item;
}
}
interface ICacheService
{
T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}
cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));
キャッシュプロバイダーは、キャッシュに「キャッシュID」という名前の何かがあるかどうかを確認し、ない場合は、デリゲートメソッドを呼び出してデータを取得し、キャッシュに保存します。
var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
私はTTの投稿を参照しており、次のアプローチを提案しています。
モデルでSystem.Web dllを参照し、System.Web.Caching.Cacheを使用
public string[] GetNames()
{
var noms = Cache["names"];
if(noms == null)
{
noms = DB.GetNames();
Cache["names"] = noms;
}
return ((string[])noms);
}
キャッシュから再読み込みされた値を返さないでください。その特定の瞬間にまだキャッシュにあるかどうかがわからないからです。前にステートメントに挿入した場合でも、既に削除されているか、キャッシュに追加されたことがない場合があります-わかりません。
そのため、データベースから読み取ったデータを追加して、キャッシュから再読み取りするのではなく、直接返します。
.NET 4.5+フレームワークの場合
参照を追加:System.Runtime.Caching
ステートメントを使用して追加:using System.Runtime.Caching;
public string[] GetNames()
{
var noms = System.Runtime.Caching.MemoryCache.Default["names"];
if(noms == null)
{
noms = DB.GetNames();
System.Runtime.Caching.MemoryCache.Default["names"] = noms;
}
return ((string[])noms);
}
.NET Framework 3.5以前のバージョンでは、ASP.NETはSystem.Web.Caching名前空間にメモリ内キャッシュの実装を提供していました。 .NET Frameworkの以前のバージョンでは、キャッシュはSystem.Web名前空間でのみ利用可能であったため、ASP.NETクラスへの依存が必要でした。 .NET Framework 4では、System.Runtime.Caching名前空間に、Webアプリケーションと非Webアプリケーションの両方向けに設計されたAPIが含まれています。
詳細:
Steve Smithは、ASP.NET MVCで彼のCachedRepositoryパターンを使用する方法を示す2つの素晴らしいブログ投稿を行いました。リポジトリパターンを効果的に使用し、既存のコードを変更せずにキャッシュを取得できます。
http://ardalis.com/Introducing-the-CachedRepository-Pattern
http://ardalis.com/building-a-cachedrepository-via-strategy-pattern
これらの2つの投稿で、彼はこのパターンを設定する方法を示し、なぜそれが役立つのかを説明しています。このパターンを使用すると、既存のコードにキャッシングロジックが表示されずにキャッシングが得られます。基本的には、キャッシュされたリポジトリを他のリポジトリであるかのように使用します。
AppFabric Cachingは分散され、複数のサーバーの物理メモリを使用してキーと値のペアでデータを保存するメモリ内キャッシング技術です。 AppFabricは、.NET Frameworkアプリケーションのパフォーマンスとスケーラビリティを改善します。 概念とアーキテクチャ
public sealed class CacheManager
{
private static volatile CacheManager instance;
private static object syncRoot = new Object();
private ObjectCache cache = null;
private CacheItemPolicy defaultCacheItemPolicy = null;
private CacheEntryRemovedCallback callback = null;
private bool allowCache = true;
private CacheManager()
{
cache = MemoryCache.Default;
callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);
defaultCacheItemPolicy = new CacheItemPolicy();
defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
defaultCacheItemPolicy.RemovedCallback = callback;
allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
}
public static CacheManager Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new CacheManager();
}
}
}
return instance;
}
}
public IEnumerable GetCache(String Key)
{
if (Key == null || !allowCache)
{
return null;
}
try
{
String Key_ = Key;
if (cache.Contains(Key_))
{
return (IEnumerable)cache.Get(Key_);
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
public void ClearCache(string key)
{
AddCache(key, null);
}
public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
{
if (!allowCache) return true;
try
{
if (Key == null)
{
return false;
}
if (cacheItemPolicy == null)
{
cacheItemPolicy = defaultCacheItemPolicy;
}
String Key_ = Key;
lock (Key_)
{
return cache.Add(Key_, data, cacheItemPolicy);
}
}
catch (Exception)
{
return false;
}
}
private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
LogManager.Instance.Info(strLog);
}
}
Hrvoje Hudoの答えの改善点を次に示します。この実装には、いくつかの重要な改善点があります。
これは、dependsOnオブジェクトをシリアル化するためにNewtonsoft.Jsonに依存していますが、他のシリアル化メソッドに簡単に交換できることに注意してください。
ICache.cs
public interface ICache
{
T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}
InMemoryCache.cs
using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;
public class InMemoryCache : ICache
{
private static readonly object CacheLockObject = new object();
public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
{
string cacheKey = GetCacheKey(getItemCallback, dependsOn);
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
lock (CacheLockObject)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
}
}
return item;
}
private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
{
var serializedDependants = JsonConvert.SerializeObject(dependsOn);
var methodType = itemCallback.GetType();
return methodType.FullName + serializedDependants;
}
}
使用法:
var order = _cache.GetOrSet(
() => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
, new { id = orderId }
, new TimeSpan(0, 10, 0)
);
@Hrvoje Hudoの答えを拡張しています...
コード:
using System;
using System.Runtime.Caching;
public class InMemoryCache : ICacheService
{
public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
{
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
{
string cacheKey = string.Format(cacheKeyFormat, id);
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback(id);
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
}
interface ICacheService
{
TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}
例
単一アイテムのキャッシング(各アイテムがそのIDに基づいてキャッシュされる場合、アイテムタイプのカタログ全体のキャッシングは集中的すぎるため)。
Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);
すべてをキャッシュする
IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);
なぜTId
2番目のヘルパーは、ほとんどのデータキーが複合ではないため、特にニースです。複合キーを頻繁に使用する場合は、追加のメソッドを追加できます。この方法では、すべての種類の文字列連結またはstring.Formatsを実行して、キーをキャッシュヘルパーに渡すのを避けることができます。また、IDをラッパーメソッドに渡す必要がないため、データアクセスメソッドの受け渡しが容易になります。ほとんどのユースケースで、全体が非常に簡潔で一貫したものになります。
2つのクラスを使用します。まず、キャッシュコアオブジェクト:
public class Cacher<TValue>
where TValue : class
{
#region Properties
private Func<TValue> _init;
public string Key { get; private set; }
public TValue Value
{
get
{
var item = HttpRuntime.Cache.Get(Key) as TValue;
if (item == null)
{
item = _init();
HttpContext.Current.Cache.Insert(Key, item);
}
return item;
}
}
#endregion
#region Constructor
public Cacher(string key, Func<TValue> init)
{
Key = key;
_init = init;
}
#endregion
#region Methods
public void Refresh()
{
HttpRuntime.Cache.Remove(Key);
}
#endregion
}
2番目は、キャッシュオブジェクトのリストです。
public static class Caches
{
static Caches()
{
Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
{
using (var context = new WordsContext())
{
return context.Languages.ToList();
}
});
}
public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}
私はこの方法でそれを使用しましたが、それは私のために機能します。 https://msdn.Microsoft.com/en-us/library/system.web.caching.cache.add(v = vs.110).aspx system.web.cachingのパラメーター情報。 cache.add。
public string GetInfo()
{
string name = string.Empty;
if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
{
name = GetNameMethod();
System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
}
else
{
name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
}
return name;
}
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
この永続的なデータの問題にシングルトンを実装することは、以前のソリューションが非常に複雑であることがわかった場合のこの問題の解決策になる可能性があります
public class GPDataDictionary
{
private Dictionary<string, object> configDictionary = new Dictionary<string, object>();
/// <summary>
/// Configuration values dictionary
/// </summary>
public Dictionary<string, object> ConfigDictionary
{
get { return configDictionary; }
}
private static GPDataDictionary instance;
public static GPDataDictionary Instance
{
get
{
if (instance == null)
{
instance = new GPDataDictionary();
}
return instance;
}
}
// private constructor
private GPDataDictionary() { }
} // singleton