Bagaimana cara menyimpan data di aplikasi MVC

252

Saya telah membaca banyak informasi tentang caching halaman dan caching halaman parsial dalam aplikasi MVC. Namun, saya ingin tahu bagaimana cara menyimpan data.

Dalam skenario saya, saya akan menggunakan LINQ untuk Entitas (kerangka kerja entitas). Pada panggilan pertama ke GetNames (atau apa pun metodenya), saya ingin mengambil data dari database. Saya ingin menyimpan hasil dalam cache dan pada panggilan kedua untuk menggunakan versi yang di-cache jika ada.

Adakah yang bisa menunjukkan contoh bagaimana ini akan bekerja, di mana ini harus dilaksanakan (model?) Dan jika itu akan berhasil.

Saya telah melihat ini dilakukan dalam aplikasi ASP.NET tradisional, biasanya untuk data yang sangat statis.

Coolcoder
sumber
1
Dalam meninjau jawaban di bawah ini, pastikan untuk mempertimbangkan apakah Anda ingin memiliki pengontrol Anda memiliki pengetahuan tentang / tanggung jawab untuk akses data dan masalah caching. Secara umum Anda ingin memisahkan ini. Lihat Pola Repositori untuk cara yang baik untuk melakukannya: deviq.com/repository-pattern
ssmith

Jawaban:

75

Referensi dll System.Web dalam model Anda dan gunakan 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;
    }

Sedikit disederhanakan tapi saya kira itu akan berhasil. Ini bukan MVC spesifik dan saya selalu menggunakan metode ini untuk menyimpan data.

terjetyl
sumber
89
Saya tidak merekomendasikan solusi ini: sebagai gantinya, Anda mungkin mendapatkan objek null lagi, karena itu membaca kembali dalam cache dan mungkin sudah dijatuhkan dari cache. Saya lebih suka melakukannya: public string [] GetNames () {string [] noms = Cache ["names"]; if (noms == null) {noms = DB.GetNames (); Cache ["names"] = noms; } return (noms); }
Oli
Saya setuju dengan Oli .. mendapatkan hasil dari panggilan aktual ke DB lebih baik daripada mendapatkannya dari cache
CodeClimber
1
Apakah ini berfungsi dengan DB.GetNames().AsQueryablemetode menunda kueri?
Chase Florell
Tidak kecuali Anda mengubah nilai balik dari string [] ke IEnumerable <string>
terjetyl
12
Jika Anda tidak menetapkan kedaluwarsa..kapan secara default cache berakhir?
Chaka
403

Inilah kelas / layanan cache pembantu yang bagus dan sederhana yang saya gunakan:

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

Pemakaian:

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

Penyedia cache akan memeriksa apakah ada sesuatu dengan nama "id cache" dalam cache, dan jika tidak ada, itu akan memanggil metode delegasi untuk mengambil data dan menyimpannya dalam cache.

Contoh:

var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
Hrvoje Hudo
sumber
3
Saya telah mengadaptasi ini sehingga mekanisme caching digunakan per sesi pengguna dengan menggunakan HttpContext.Current.Session sebagai gantinya. Saya juga telah meletakkan properti Cache pada kelas BaseController saya sehingga aksesnya yang mudah dan memperbarui konstruktor memungkinkan DI untuk pengujian unit. Semoga ini membantu.
WestDiscGolf
1
Anda juga dapat membuat kelas dan metode ini statis untuk dapat digunakan kembali di antara pengontrol lainnya.
Alex
5
Kelas ini seharusnya tidak bergantung pada HttpContext. Saya menyederhanakannya hanya untuk tujuan contoh di sini. Objek cache harus dimasukkan melalui konstruktor - dapat diganti kemudian dengan mekanisme caching lainnya. Semua ini dicapai dengan IoC / DI, bersama dengan siklus hidup statis (tunggal).
Hrvoje Hudo
3
@ Brendan - dan lebih buruk lagi, ia memiliki string ajaib di tempat untuk kunci cache, daripada menyimpulkannya dari nama metode dan parameter.
ssmith
5
Ini adalah solusi tingkat rendah yang luar biasa. Seperti yang telah disinggung orang lain, Anda ingin membungkusnya dalam tipe aman, kelas khusus domain. Mengakses ini langsung di pengontrol Anda akan menjadi mimpi buruk pemeliharaan karena string ajaib.
Josh Noe
43

Saya merujuk ke posting TT dan menyarankan pendekatan berikut:

Referensi dll System.Web dalam model Anda dan gunakan System.Web.Caching.Cache

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

    return ((string[])noms);
}

Anda seharusnya tidak mengembalikan nilai yang dibaca ulang dari cache, karena Anda tidak akan pernah tahu apakah pada saat itu masih dalam cache. Bahkan jika Anda memasukkannya dalam pernyataan sebelumnya, itu mungkin sudah hilang atau belum pernah ditambahkan ke cache - Anda tidak tahu.

Jadi Anda menambahkan data yang dibaca dari database dan mengembalikannya secara langsung, bukan membaca ulang dari cache.

Oli
sumber
Tapi bukankah baris tersebut Cache["names"] = noms;dimasukkan ke dalam cache?
Omar
2
@Baddie Ya, benar. Tapi contoh ini berbeda dengan Oli pertama yang dirujuk, karena dia tidak mengakses cache lagi - masalahnya adalah hanya melakukan: return (string []) Cache ["names"]; .. BISA menghasilkan nilai nol yang dikembalikan, karena BISA telah kedaluwarsa. Itu tidak mungkin, tetapi itu bisa terjadi. Contoh ini lebih baik, karena kami menyimpan nilai aktual yang dikembalikan dari db dalam memori, cache nilai itu, dan kemudian mengembalikan nilai itu, bukan nilai yang dibaca ulang dari cache.
jamiebarrow
Atau ... nilai baca ulang dari cache, jika masih ada (! = Null). Karenanya, seluruh titik caching. Ini hanya untuk mengatakan bahwa ia memeriksa ulang nilai-nilai nol, dan membaca database jika perlu. Sangat cerdas, terima kasih Oli!
Sean Kendle
Bisakah Anda membagikan beberapa tautan tempat saya dapat membaca tentang caching aplikasi berbasis Nilai Utama Saya tidak dapat menemukan tautan.
Unbreakable
@Oli, Cara Mengkonsumsi catatan Tembolok ini dari laman CSHTML atau HTML
Deepan Raj
37

Untuk .NET 4.5+ framework

tambahkan referensi: System.Runtime.Caching

tambahkan menggunakan pernyataan: 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);
}

Dalam .NET Framework 3.5 dan versi sebelumnya, ASP.NET menyediakan implementasi cache dalam memori di System.Web.Caching namespace. Dalam versi sebelumnya dari .NET Framework, caching hanya tersedia di namespace System.Web dan karenanya memerlukan ketergantungan pada kelas ASP.NET. Dalam .NET Framework 4, namespace System.Runtime.Caching berisi API yang dirancang untuk aplikasi Web dan non-Web.

Info lebih lanjut:

juFo
sumber
Dari mana Db.GerNames()datangnya?
Junior
DB.GetNames hanyalah metode dari DAL yang mengambil beberapa nama dari database. Ini adalah apa pun yang biasanya Anda ambil.
juFo
Ini harus di atas karena memiliki solusi yang relevan saat ini
BYISHIMO Audace
2
Terima kasih, perlu menambahkan paket nuget System.Runtime.Caching juga (v4.5).
Steve Greene
26

Steve Smith melakukan dua posting blog luar biasa yang menunjukkan cara menggunakan pola CachedRepository di ASP.NET MVC. Ini menggunakan pola repositori secara efektif dan memungkinkan Anda mendapatkan caching tanpa harus mengubah kode yang ada.

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

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

Dalam dua posting ini dia menunjukkan kepada Anda bagaimana mengatur pola ini dan juga menjelaskan mengapa ini berguna. Dengan menggunakan pola ini Anda mendapatkan caching tanpa kode yang ada melihat logika caching. Pada dasarnya Anda menggunakan repositori dalam cache seolah-olah itu adalah repositori lainnya.

Brendan Enrick
sumber
1
Pos luar biasa! Terima kasih telah berbagi!!
Mark Good
Tautan mati pada 2013-08-31.
CBono
3
Konten telah dipindahkan sekarang. ardalis.com/Introducing- the - CachedRepository
Uchitha
Bisakah Anda membagikan beberapa tautan tempat saya dapat membaca tentang caching aplikasi berbasis Nilai Utama Saya tidak dapat menemukan tautan.
Unbreakable
4

Caching AppFabric didistribusikan dan teknik caching dalam memori yang menyimpan data dalam pasangan nilai kunci menggunakan memori fisik di beberapa server. AppFabric memberikan peningkatan kinerja dan skalabilitas untuk aplikasi .NET Framework. Konsep dan Arsitektur

Arun Duth
sumber
Ini khusus untuk Azure, bukan ASP.NET MVC secara umum.
Henry C
3

Memperluas jawaban @Hrvoje Hudo ...

Kode:

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

Contohnya

Caching item tunggal (ketika setiap item di-cache berdasarkan ID-nya karena caching seluruh katalog untuk jenis item akan terlalu intensif).

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

Caching semua hal

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

Mengapa TId

Pembantu kedua sangat baik karena sebagian besar kunci data tidak komposit. Metode tambahan dapat ditambahkan jika Anda sering menggunakan kunci komposit. Dengan cara ini Anda menghindari melakukan segala macam penggabungan string atau string. Format untuk mendapatkan kunci untuk meneruskan ke penolong cache. Ini juga membuat melewati metode akses data lebih mudah karena Anda tidak harus memasukkan ID ke dalam metode pembungkus ... semuanya menjadi sangat singkat dan konsisten untuk sebagian besar kasus penggunaan.

smdrager
sumber
1
Definisi antarmuka Anda tidak memiliki param "lengthInMinutes". ;-)
Tech0
3

Inilah peningkatan jawaban Hrvoje Hudo. Implementasi ini memiliki beberapa peningkatan utama:

  • Kunci cache dibuat secara otomatis berdasarkan fungsi untuk memperbarui data dan objek yang disahkan yang menentukan dependensi
  • Lewati rentang waktu untuk durasi cache apa pun
  • Menggunakan kunci untuk keamanan ulir

Perhatikan bahwa ini memiliki ketergantungan pada Newtonsoft.Json untuk membuat serialisasi objek dependOn, tetapi itu dapat dengan mudah diganti untuk metode serialisasi lainnya.

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

Pemakaian:

var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);
DShook
sumber
2
The if (item == null)harus berada di dalam kunci. Sekarang ketika ini ifsebelum kunci, kondisi lomba dapat terjadi. Atau bahkan lebih baik, Anda harus menyimpan ifsebelum kunci, tetapi periksa kembali apakah cache masih kosong sebagai baris pertama di dalam kunci. Karena jika dua utas datang pada saat yang sama, keduanya memperbarui cache. Kunci Anda saat ini tidak membantu.
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);
    }
}
Chau
sumber
3
Pertimbangkan untuk menambahkan beberapa penjelasan
Mike Debela
2

Saya telah menggunakannya dengan cara ini dan itu bekerja untuk saya. https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx parameter info untuk 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;

}
pengguna3776645
sumber
upvotes tambahan untuk hal-hal yang sepenuhnya memenuhi syarat dengan namespace lengkapnya !!
Ninjanoel
1

Saya menggunakan dua kelas. Pertama, objek inti cache:

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
}

Yang kedua adalah daftar objek cache:

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; }
}
Berezh
sumber
0

Saya akan mengatakan menerapkan Singleton pada masalah data yang ada dapat menjadi solusi untuk masalah ini jika Anda menemukan solusi sebelumnya jauh lebih rumit

 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
sumber
Ini bekerja dengan baik bagi saya, itulah sebabnya saya merekomendasikan hal ini kepada semua orang yang mungkin terbantu dengan ini
GeraGamo
0
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
Md. Akhtar Uzzaman
sumber
-8

Anda juga dapat mencoba dan menggunakan caching yang ada di dalam ASP MVC:

Tambahkan atribut berikut ke metode pengontrol yang ingin Anda cache:

[OutputCache(Duration=10)]

Dalam hal ini, ActionResult ini akan di-cache selama 10 detik.

Lebih lanjut tentang ini di sini

qui
sumber
4
OutputCache adalah untuk rendering Action, pertanyaannya adalah sehubungan dengan caching data bukan halaman.
Coolcoder
itu off topic tapi OutputCache juga men-cache data database
Muflix