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.