MVCアプリケーションでデータをキャッシュする方法


252

MVCアプリケーションでのページキャッシングと部分ページキャッシングに関する多くの情報を読みました。ただし、データをキャッシュする方法を教えてください。

私のシナリオでは、LINQ to Entities(エンティティフレームワーク)を使用します。GetNames(またはメソッドが何であれ)の最初の呼び出しで、データベースからデータを取得します。結果をキャッシュに保存し、2番目の呼び出しで、キャッシュされたバージョンが存在する場合はそれを使用する必要があります。

これがどのように機能するか、これを実装する必要がある場所(モデル?)、および機能するかどうかの例を誰かに示すことはできますか?

これは従来のASP.NETアプリで行われ、通常は非常に静的なデータで行われます。


1
以下の回答を確認する際は、コントローラーにデータアクセスとキャッシュの問題に関する知識と責任があるかどうかを必ず検討してください。通常、これを分離する必要があります。そのための適切な方法については、リポジトリパターンを参照してください:deviq.com/repository-pattern
ssmith

回答:


75

モデルで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固有ではなく、私は常にこの方法を使用してデータをキャッシュしています。


89
このソリューションはお勧めしません。戻り値では、nullオブジェクトを再度取得する可能性があります。これは、オブジェクトがキャッシュ内で再読み取り中であり、すでにキャッシュから削除されている可能性があるためです。むしろ、次のようにします。public string [] GetNames(){string [] noms = Cache ["names"]; if(noms == null){noms = DB.GetNames(); Cache ["names"] = noms; } return(noms); }
オリ

私はOliに同意します。DBへの実際の呼び出しから結果を取得する方が、キャッシュから取得するよりも優れています
CodeClimber '08 / 07/21

1
これDB.GetNames().AsQueryableはクエリを遅延させる方法で機能しますか?
Chase Florell、2010

string []からIEnumerable <string>に戻り値を変更しない限り
terjetyl

12
有効期限を設定しない場合、デフォルトでキャッシュはいつ期限切れになりますか?
チャカ2013

403

ここに私が使う素敵でシンプルなキャッシュヘルパークラス/サービスがあります:

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())

3
代わりにHttpContext.Current.Sessionを使用して、ユーザーセッションごとにキャッシュメカニズムが使用されるようにこれを調整しました。また、BaseControllerクラスにCacheプロパティを配置して、簡単にアクセスし、コンストラクターを更新して、単体テスト用のDIを可能にしました。お役に立てれば。
WestDiscGolf

1
他のコントローラー間で再利用できるように、このクラスとメソッドを静的にすることもできます。
Alex

5
このクラスはHttpContextに依存しないでください。ここでは、例を示すために単純化しました。キャッシュオブジェクトはコンストラクタを介して挿入する必要があります。他のキャッシュメカニズムで置き換えることができます。これはすべて、静的(シングルトン)ライフサイクルとともに、IoC / DIで実現されます。
Hrvoje Hudo 2010

3
@Brendan-さらに悪いことに、メソッド名とパラメーターからキャッシュキーを推測するのではなく、キャッシュキーのマジックストリングが配置されています。
ssmith 2011年

5
これは素晴らしい低レベルのソリューションです。他の人が言及したように、これをタイプセーフなドメイン固有のクラスでラップする必要があります。コントローラーで直接アクセスすることは、魔法の文字列のため、メンテナンスの悪夢になります。
Josh Noe 2013

43

私は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);
}

キャッシュから再度読み取った値を返すべきではありません。その特定の時点で値がまだキャッシュ内にあるかどうかわからないためです。以前にステートメントに挿入した場合でも、すでになくなっているか、キャッシュに追加されていない可能性があります。

そのため、データベースから読み取ったデータを追加して、キャッシュから再度読み取るのではなく、直接返します。


しかし、その行Cache["names"] = noms;はキャッシュに入りませんか?
Omar

2
@Baddieはい、あります。しかし、この例は、Oliが再びキャッシュにアクセスしないため、最初のOliが参照しているものとは異なります。問題は、次のようにすることだけです。return(string [])Cache ["names"]; ..有効期限が切れているため、NULL値が返される可能性があります。可能性は低いですが、発生する可能性があります。この例の方が優れています。dbから返された実際の値をメモリに格納し、その値をキャッシュしてから、その値を返します。キャッシュから再度読み取った値ではありません。
jamiebarrow 2010年

または...値がまだ存在する場合は、キャッシュから再度読み取ります(!= null)。したがって、キャッシングの全体のポイント。これは、null値をダブルチェックし、必要に応じてデータベースを読み取るというだけのことです。非常に賢い、オリ!
Sean Kendle 2015年

Key Valueベースのアプリケーションキャッシングについて読んでいただけるリンクを教えてください。リンクが見つかりません。
アンブレイカブル2017

@Oli、CSHTMLまたはHTMLページからこのキャッシュレコードを消費する方法
Deepan Raj

37

.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が含まれています。

より詳しい情報:


どこDb.GerNames()から来たの?
ジュニア

DB.GetNamesは、データベースからいくつかの名前をフェッチするDALのメソッドにすぎません。これは通常取得するものです。
juFo 2017年

それは現在、関連するソリューションを持っているので、これは一番上にする必要があります
BYISHIMO AUDACE

2
おかげで、System.Runtime.Caching nugetパッケージも追加する必要がありました(v4.5)。
スティーブグリーン

26

Steve SmithがASP.NET MVCで彼のCachedRepositoryパターンを使用する方法を示す2つの素晴らしいブログ投稿を行いました。リポジトリパターンを効果的に使用し、既存のコードを変更せずにキャッシュを取得できます。

http://ardalis.com/Introducing-the-CachedRepository-Pattern

http://ardalis.com/building-a-cachedrepository-via-strategy-pattern

これらの2つの投稿で、彼はこのパターンを設定する方法を示し、なぜそれが役立つのかを説明しています。このパターンを使用することにより、既存のコードがキャッシングロジックをまったく見なくてもキャッシングが行われます。基本的に、キャッシュされたリポジトリを他のリポジトリと同じように使用します。


1
素晴らしい投稿!共有してくれてありがとう!!
マークグッド

2013-08-31現在、リンクは停止しています。
CBono 2013


Key Valueベースのアプリケーションキャッシングについて読んでいただけるリンクを教えてください。リンクが見つかりません。
アンブレイカブル2017

4

AppFabricキャッシングは分散されており、複数のサーバー間で物理メモリを使用してデータをキーと値のペアに格納するインメモリキャッシング技術です。AppFabricは、.NET Frameworkアプリケーションのパフォーマンスとスケーラビリティを改善します。概念とアーキテクチャ


これはAzureに固有であり、ASP.NET MVC全般ではありません。
Henry C

3

@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をラッパーメソッドに渡す必要がないため、データアクセスメソッドの受け渡しも簡単になります。大部分のユースケースでは、全体が非常に簡潔で一貫性のあるものになります。


1
インターフェイス定義に "durationInMinutes"パラメータがありません。;-)
Tech0

3

Hrvoje Hudoの回答の改善点を以下に示します。この実装には、いくつかの重要な改善点があります。

  • キャッシュキーは、データを更新する関数と、依存関係を指定する渡されたオブジェクトに基づいて自動的に作成されます
  • キャッシュ期間の期間を渡す
  • スレッドセーフのためにロックを使用

これは、dependonsOnオブジェクトをシリアル化するために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)
);

2
if (item == null)ロック内にある必要があります。これifがロックの前になると、競合状態が発生する可能性があります。あるいはif、ロックの前に保持する必要がありますが、ロック内の最初の行としてキャッシュがまだ空かどうかを再確認してください。2つのスレッドが同時に来た場合、両方がキャッシュを更新するためです。現在のロックは役に立ちません。
アルケップ

3
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);
    }
}

3
説明を追加することを検討してください
Mike Debela

2

私はこの方法でそれを使用したことがあり、それは私のために機能します。 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;

}

完全な名前空間で完全に修飾されたものに対する追加の賛成投票!!
ニンジャノエル

1

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; }
}

0

この永続的なデータの問題にシングルトンを実装することは、以前のソリューションが非常に複雑である場合に備えて、この問題のソリューションになる可能性があると言います

 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

これは私がこれによって助けすることができるすべての人にこれをお勧めする理由つまり完全に私のために働いた
GeraGamo


-8

ASP MVCに組み込まれたキャッシングを試して使用することもできます。

キャッシュするコントローラーメソッドに次の属性を追加します。

[OutputCache(Duration=10)]

この場合、このActionResultは10秒間キャッシュされます。

詳細はこちら


4
OutputCacheはActionのレンダリング用であり、問​​題はページではなくデータのキャッシュに関するものでした。
Coolcoder 2008

トピック外ですが、OutputCacheはデータベースデータもキャッシュします
Muflix
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.