MVC uygulamasında veri önbellekleme


252

Bir MVC uygulamasında sayfa önbellekleme ve kısmi sayfa önbellekleme hakkında birçok bilgi okudum. Ancak, verileri nasıl önbelleğe alacağınızı bilmek istiyorum.

Senaryomda LINQ to Entities (varlık çerçevesi) kullanacağım. GetNames (veya yöntem ne olursa olsun) ilk çağrıda ben veritabanından veri kapmak istiyorum. Sonuçları önbellekte ve varsa önbelleğe alınmış sürümü kullanmak için ikinci çağrıda kaydetmek istiyorum.

Herkes bunun nasıl çalışacağına, bunun nereye uygulanacağına (model?) Ve işe yarayıp yaramayacağına dair bir örnek gösterebilir.

Bunu genellikle çok statik veriler için geleneksel ASP.NET uygulamalarında yaptım.


1
Aşağıdaki yanıtları incelerken, denetleyicinizin veri erişimi ve önbellekleme endişeleri hakkında bilgi sahibi olmasını isteyip istemediğinizi göz önünde bulundurun. Genellikle bunu ayırmak istersiniz. Bunu yapmanın iyi bir yolu için Havuz Deseni'ne bakın
ssmith

Yanıtlar:


75

Modelinizde System.Web dll'sine bakın ve System.Web.Caching.Cache kullanın

    public string[] GetNames()
    {
      string[] names = Cache["names"] as string[];
      if(names == null) //not in cache
      {
        names = DB.GetNames();
        Cache["names"] = names;
      }
      return names;
    }

Biraz basitleştirilmiş ama sanırım bu işe yarar. Bu MVC'ye özgü değildir ve her zaman veri önbelleğe almak için bu yöntemi kullandım.


89
Bu çözümü önermiyorum: dönüşte, yine boş bir nesne alabilirsiniz, çünkü önbellekte yeniden okunuyor ve zaten önbellekten çıkarılmış olabilir. Yapmayı tercih ederim: public string [] GetNames () {string [] noms = Cache ["names"]; if (noms == null) {noms = DB.GetNames (); Önbellek ["adlar"] = noms; } dönüş (noms); }
Oli

Katılıyorum Oli .. DB gerçek çağrı sonuçları almak önbellek onları almak daha iyidir
CodeClimber

1
Bu DB.GetNames().AsQueryable, sorguyu geciktirme yöntemiyle çalışır mı ?
Chase Florell

Dönüş değerini [] dizesinden IEnumerable <string> olarak değiştirmediğiniz sürece değil
terjetyl

12
Son kullanma tarihini ayarlamazsanız ... önbellek varsayılan olarak ne zaman dolar?
Chaka

403

İşte kullandığım güzel ve basit bir önbellek yardımcı sınıfı / hizmeti:

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

Kullanımı:

cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));

Önbellek sağlayıcısı, önbellekte "önbellek kimliği" adında bir şey olup olmadığını kontrol eder ve yoksa, verileri almak ve önbellekte saklamak için bir temsilci yöntemi çağırır.

Misal:

var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())

3
Bunu, önbellek mekanizması HttpContext.Current.Session kullanarak kullanıcı oturumu başına kullanılacak şekilde adapte ettik. Ayrıca kolay erişim ve güncellenmiş yapıcı birim test için DI izin böylece BaseController sınıfımda bir Cache özelliği koydum. Bu yardımcı olur umarım.
WestDiscGolf

1
Bu sınıfı ve yöntemi diğer denetleyiciler arasında yeniden kullanılabilirlik için de statik hale getirebilirsiniz.
Alex

5
Bu sınıf HttpContext'e bağlı olmamalıdır. Ben sadece buradaki amaç için basitleştirdim. Önbellek nesnesi yapıcı aracılığıyla eklenmelidir - daha sonra diğer önbellekleme mekanizmalarıyla değiştirilebilir. Bütün bunlar, IoC / DI ile birlikte statik (singleton) yaşam döngüsü ile elde edilir.
Hrvoje Hudo

3
@Brendan - ve daha da kötüsü, önbellek anahtarları için yöntem adı ve parametrelerinden çıkarım yerine sihirli dizeler var.
ssmith

5
Bu harika bir düşük seviye çözümdür. Diğerlerinin de belirttiği gibi, bunu güvenli, alana özgü bir sınıfa sarmak istersiniz. Buna doğrudan kontrolörlerinizden erişmek, sihirli tellerden dolayı bir bakım kabusu olurdu.
Josh Noe

43

TT'nin gönderisine atıfta bulunuyorum ve aşağıdaki yaklaşımı öneriyorum:

Modelinizde System.Web dll'sine bakın ve System.Web.Caching.Cache kullanın

public string[] GetNames()
{ 
    var noms = Cache["names"];
    if(noms == null) 
    {    
        noms = DB.GetNames();
        Cache["names"] = noms; 
    }

    return ((string[])noms);
}

Önbellekten yeniden okunan bir değer döndürmemelisiniz, çünkü o anda o anda hala önbellekte olup olmadığını asla bilemezsiniz. Daha önce ifadeye eklemiş olsanız bile, zaten yok olmuş veya önbelleğe hiç eklenmemiş olabilir - sadece bilmiyorsunuz.

Böylece veritabanından okunan verileri ekler ve önbellekten yeniden okumak yerine doğrudan döndürürsünüz.


Ama çizgi Cache["names"] = noms;önbelleğe koymuyor mu?
Omar

2
@Baddie Evet öyle. Ancak bu örnek, Oli'nin bahsettiği ilk örnekten farklıdır, çünkü önbelleğe tekrar erişmez - sorun şu ki: return (string []) Cache ["names"]; .. BOŞALTABİLDİ çünkü boş bir değer döndürülebilir. Muhtemel değil, ama olabilir. Bu örnek daha iyidir, çünkü db'den döndürülen gerçek değeri bellekte saklarız, bu değeri önbelleğe alır ve sonra önbellekten yeniden okunan değeri değil, bu değeri döndürürüz.
jamiebarrow

Veya ... değer hala mevcutsa önbellekten yeniden okunur (! = Null). Bu nedenle, tüm önbellekleme noktası. Bu sadece boş değerleri iki kere kontrol ettiğini ve gerektiğinde veritabanını okuduğunu söylemek içindir. Çok akıllı, teşekkürler Oli!
Sean Kendle

Anahtar Değer tabanlı uygulama önbelleğe alma hakkında okuyabileceğim bir bağlantı paylaşır mısınız? Bağlantı bulamıyorum.
Kırılmaz

@Oli, CSHTML veya HTML sayfasından bu Önbellek kayıtlarını kullanma
Deepan Raj

37

.NET 4.5+ çerçevesi için

referans ekle: System.Runtime.Caching

deyim kullanarak ekle: 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 ve önceki sürümlerinde ASP.NET, System.Web.Caching ad alanında bir bellek içi önbellek uygulaması sağlamıştır. .NET Framework'ün önceki sürümlerinde, önbellekleme yalnızca System.Web ad alanında kullanılabilirdi ve bu nedenle ASP.NET sınıflarına bağımlılık gerektiriyordu. .NET Framework 4'te, System.Runtime.Caching ad alanı, hem Web hem de Web dışı uygulamalar için tasarlanmış API'ler içerir.

Daha fazla bilgi:


Nereden Db.GerNames()geliyor?
Genç

DB.GetNames, veritabanından bazı adları alan DAL'ın yalnızca bir yöntemidir. Normalde ne alırsanız alın budur.
juFo

Bu akım, ilgili çözüm olarak üst üste olmalıdır
BYISHIMO Audace

2
Teşekkürler, System.Runtime.Caching nuget paketini de eklemeniz gerekiyor (v4.5).
Steve Greene

26

Steve Smith, ASP.NET MVC'de CachedRepository modelini nasıl kullanacağını gösteren iki harika blog yazısı yaptı. Havuz desenini etkili bir şekilde kullanır ve mevcut kodunuzu değiştirmek zorunda kalmadan önbelleğe almanızı sağlar.

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

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

Bu iki yazıda size bu kalıbın nasıl kurulacağını ve neden yararlı olduğunu açıklar. Bu kalıbı kullanarak, mevcut kodunuz önbellek mantığını görmeden önbelleğe alırsınız. Esasen, önbelleğe alınmış havuzu sanki başka bir havuzmuş gibi kullanırsınız.


1
Harika gönderiler! Paylaşım için teşekkürler!!
Mark Good

2013-08-31 itibariyle bağlantılar kesildi.
CBono


Anahtar Değer tabanlı uygulama önbelleğe alma hakkında okuyabileceğim bir bağlantı paylaşır mısınız? Bağlantı bulamıyorum.
Kırılmaz

4

AppFabric Caching dağıtılır ve verileri birden çok sunucuda fiziksel bellek kullanarak anahtar / değer çiftlerinde depolayan bir bellek içi önbellekleme tekniği. AppFabric, .NET Framework uygulamaları için performans ve ölçeklenebilirlik geliştirmeleri sağlar. Kavramlar ve Mimari


Bu genel olarak ASP.NET MVC'ye değil Azure'a özgüdür.
Henry C

3

@Hrvoje Hudson'un cevabı genişletiliyor ...

Kod:

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

Örnekler

Tek öğe önbellekleme (öğe türü için tüm kataloğun önbelleğe alınması çok yoğun olacağından, her öğe kimliğine göre önbelleğe alındığında).

Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);

Bir şeyi önbelleğe alma

IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);

Neden TId

İkinci yardımcı özellikle güzeldir çünkü çoğu veri anahtarı bileşik değildir. Bileşik anahtarları sık kullanırsanız ek yöntemler eklenebilir. Bu şekilde önbellek yardımcısına geçmek için anahtar almak için her türlü dize birleştirme veya string.Formats yapmaktan kaçının. Ayrıca, veri erişim yöntemini geçirmeyi de kolaylaştırır, çünkü kimliği sarma yöntemine geçirmeniz gerekmez ... her şey kullanım vakalarının çoğu için çok kısa ve tutarlı hale gelir.


1
Arabirim tanımlarınızda "durationInMinutes" parametresi eksik. ;-)
Tech0

3

İşte Hrvoje Hudson'un cevabı için bir gelişme. Bu uygulamanın birkaç önemli geliştirmesi var:

  • Önbellek anahtarları, verileri güncelleme işlevine ve bağımlılıkları belirten iletilen nesneye göre otomatik olarak oluşturulur
  • Herhangi bir önbellek süresi için zaman aralığını geçirme
  • İplik güvenliği için bir kilit kullanır

Bu, dependsOn nesnesini serileştirmek için Newtonsoft.Json'a bağımlı olduğunu, ancak bunun diğer herhangi bir serileştirme yöntemi için kolayca değiştirilebileceğini unutmayın.

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

Kullanımı:

var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);

2
if (item == null)Kilidi içinde olmalıdır. Şimdi bu ifkilitlenmeden önce, yarış durumu oluşabilir. Ya da daha iyisi, ifkilitten önce tutmalısınız , ancak önbellek kilidin içindeki ilk satır olarak hala boş olup olmadığını kontrol edin. Çünkü iki iş parçacığı aynı anda gelirse, ikisi de önbelleği güncelleştirir. Geçerli kilidiniz yardımcı olmuyor.
Al Kepp

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
Biraz açıklama eklemeyi düşünün
Mike Debela

2

Bu şekilde kullandım ve benim için çalışıyor. system.web.caching.cache.add için https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx parametre bilgileri.

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;

}

tam ad alanı ile tam nitelikli şeyler için ekstra upvotes !!
Ninjanoel

1

İki sınıf kullanıyorum. İlki önbellek çekirdek nesnesidir:

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
}

İkincisi, önbellek nesnelerinin listesidir:

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

Önceki kalıcı çözümleri çok karmaşık bulmanız durumunda, Singleton'un bu sürekli veri sorunu üzerine uygulanmasının bu sorun için bir çözüm olabileceğini söyleyeceğim

 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

Bu benim için mükemmel çalıştı Bu yüzden bu yardımcı olabilir herkese tavsiye
GeraGamo


-8

ASP MVC'de yerleşik önbelleği de deneyebilir ve kullanabilirsiniz:

Önbelleğe almak istediğiniz denetleyici yöntemine aşağıdaki özelliği ekleyin:

[OutputCache(Duration=10)]

Bu durumda, bunun ActionResult sonucu 10 saniye boyunca önbelleğe alınır.

Burada daha fazlası


4
OutputCache, Eylemin oluşturulması içindir, soru sayfanın değil önbellek verisiyle ilgilidir.
Coolcoder

konu dışı ama OutputCache veritabanı verilerini de önbelleğe alıyor
Muflix
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.