MVC 응용 프로그램에서 데이터를 캐시하는 방법


252

MVC 응용 프로그램의 페이지 캐싱 및 부분 페이지 캐싱에 대한 많은 정보를 읽었습니다. 그러나 데이터를 캐시하는 방법을 알고 싶습니다.

내 시나리오에서는 LINQ to Entities (엔터티 프레임 워크)를 사용합니다. GetNames (또는 메소드가 무엇이든)에 대한 첫 번째 호출에서 데이터베이스의 데이터를 가져오고 싶습니다. 캐시 된 버전을 사용하기 위해 결과를 캐시에 저장하고 두 번째 호출에서 결과를 저장하고 싶습니다.

누구나 이것이 어떻게 작동하는지, 어디에서 구현 해야하는지 (모델?) 및 작동하는지에 대한 예를 보여줄 수 있습니까?

나는 전형적인 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 (); 캐시 [ "이름"] = noms; } 반환 (noms); }
— Oli

Oli.에 동의합니다. DB에 대한 실제 호출에서 결과를 얻는 것이 캐시에서 얻는 것보다 낫습니다
— CodeClimber

1
이것은 DB.GetNames().AsQueryable쿼리 지연 방법 과 함께 작동합니까 ?
— 체이스 플 로렐

회원님이는 IEnumerable <문자열>에 문자열 []에서 반환 값을 변경하지 않는 한
— terjetyl

12
만료를 설정하지 않으면 캐시가 기본적으로 언제 만료됩니까?
— 차카

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

3
@Brendan-그리고 더 나쁘게도 메소드 이름과 매개 변수에서 추론하는 대신 캐시 키에 대한 마법 문자열이 있습니다.
— ssmith

5
이것은 놀라운 저수준 솔루션입니다. 다른 사람들이 암시 한 것처럼, 이것을 안전한 도메인 전용 클래스로 포장하고 싶을 것입니다. 컨트롤러에서 직접 액세스하는 것은 마법의 끈 때문에 유지 보수의 악몽이 될 것입니다.
— Josh Noe

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가 언급 한 첫 번째와 다릅니다. 문제는 다음과 같이하는 것입니다. return (string []) Cache [ "names"]; .. COULD가 만료되어 널값이 리턴됩니다. 가능성은 없지만 일어날 수 있습니다. 이 예제는 db에서 반환 된 실제 값을 메모리에 저장하고 해당 값을 캐시 한 다음 캐시에서 다시 읽은 값이 아니라 해당 값을 반환하기 때문에 더 좋습니다.
— jamiebarrow

또는 ... 값이 여전히 존재하는 경우 캐시에서 다시 읽습니다 (! = null). 따라서 캐싱의 전체 지점입니다. 이것은 null 값을 두 번 확인하고 필요한 경우 데이터베이스를 읽습니다. 매우 영리합니다. Oli!
— Sean Kendle

키 값 기반 애플리케이션 캐싱에 대해 읽을 수있는 링크를 공유해 주시겠습니까? 링크를 찾을 수 없습니다.
— 깨지지 않는

@Oli, CSHTML 또는 HTML 페이지에서이 캐시 레코드를 사용하는 방법
— Deepan Raj

37

.NET 4.5+ 프레임 워크

참조 추가 : System.Runtime.Caching

using 문 추가 : 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 네임 스페이스에는 웹 및 웹 이외의 응용 프로그램 모두를 위해 설계된 API가 포함되어 있습니다.

더 많은 정보:


어디에서 Db.GerNames()오나요?
— Junior

DB.GetNames는 데이터베이스에서 일부 이름을 가져 오는 DAL의 메소드 일뿐입니다. 이것은 일반적으로 검색하는 것입니다.
— juFo

이것은 현재 관련 솔루션을 가지고 상단에 있어야합니다
— BYISHIMO Audace

2
감사합니다 .System.Runtime.Caching nuget 패키지 (v4.5)도 추가해야했습니다.
— Steve Greene

26

Steve Smith는 ASP.NET MVC에서 CachedRepository 패턴을 사용하는 방법을 보여주는 두 개의 훌륭한 블로그 게시물을 작성했습니다. 리포지토리 패턴을 효과적으로 사용하므로 기존 코드를 변경하지 않고도 캐싱을 수행 할 수 있습니다.

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

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

이 두 게시물에서 그는이 패턴을 설정하는 방법을 보여주고 왜 유용한 지 설명합니다. 이 패턴을 사용하면 기존 코드 없이도 캐싱 로직을 보지 않고도 캐싱 할 수 있습니다. 기본적으로 캐시 된 저장소는 다른 저장소 인 것처럼 사용합니다.


1
좋은 소식! 공유해 주셔서 감사합니다 !!
— Mark Good

2013 년 8 월 31 일 현재 링크가 끊어졌습니다.
— CBono


키 값 기반 애플리케이션 캐싱에 대해 읽을 수있는 링크를 공유해 주시겠습니까? 링크를 찾을 수 없습니다.
— 깨지지 않는

4

AppFabric Caching 은 여러 서버의 물리적 메모리를 사용하여 키-값 쌍으로 데이터를 저장하는 인 메모리 캐싱 기술입니다. AppFabric은 .NET Framework 응용 프로그램의 성능 및 확장 성 향상을 제공합니다. 개념과 아키텍처


이것은 일반적으로 ASP.NET MVC가 아닌 Azure에만 해당됩니다.
— 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);

왜 TI

두 번째 도우미는 대부분의 데이터 키가 복합적이지 않기 때문에 특히 좋습니다. 복합 키를 자주 사용하는 경우 추가 방법을 추가 할 수 있습니다. 이런 식으로 모든 종류의 문자열 연결 또는 문자열을 피할 수 있습니다. 키를 캐시 도우미에 전달하기위한 형식. 또한 래퍼 메소드에 ID를 전달할 필요가 없기 때문에 데이터 액세스 메소드를 더 쉽게 전달할 수 있습니다. 모든 것이 매우 간결하고 대부분의 사용 사례에 맞게 구성됩니다.


1
인터페이스 정의에 "durationInMinutes"매개 변수가 누락되었습니다. ;-)
— Tech0

3

Hrvoje Hudo의 답변이 개선되었습니다. 이 구현에는 몇 가지 주요 개선 사항이 있습니다.

  • 캐시 키는 데이터를 업데이트하는 기능과 종속성을 지정하는 전달 된 객체를 기반으로 자동 생성됩니다.
  • 모든 캐시 지속 시간에 대한 시간 범위 통과
  • 나사산 안전을 위해 잠금 장치 사용

이에 따라 dependOn 객체를 직렬화하기 위해 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잠금을 유지 하기 전에 잠금 내부의 첫 번째 행으로 캐시가 여전히 비어 있는지 다시 확인해야합니다. 두 개의 스레드가 동시에 오면 캐시를 모두 업데이트하기 때문입니다. 현재 잠금이 도움이되지 않습니다.
— 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
몇 가지 설명을 추가해보십시오
— Mike Debela

2

나는 이런 식으로 그것을 사용했고 그것은 나를 위해 작동합니다. system.web.caching.cache.add에 대한 https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx 매개 변수 정보

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;

}

전체 네임 스페이스를 사용하여 자격을 갖춘 제품에 대한 추가 지지자 !!
— Ninjanoel

1

나는 두 개의 수업을 사용합니다. 먼저 캐시 코어 객체 :

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
}

두 번째는 캐시 객체 목록입니다.

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

이 지속적인 데이터 문제에 대해 Singleton을 구현하면 이전 솔루션이 훨씬 복잡한 경우이 문제에 대한 해결책이 될 수 있습니다.

 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

주제가 아니지만 OutputCache는 데이터베이스 데이터도 캐시합니다.
— Muflix
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.