【问题标题】:How to cache data in a MVC application如何在 MVC 应用程序中缓存数据
【发布时间】:2010-09-25 12:38:13
【问题描述】:

我已经阅读了大量关于 MVC 应用程序中的页面缓存和部分页面缓存的信息。不过,我想知道您将如何缓存数据。

在我的场景中,我将使用 LINQ to Entities(实体框架)。在第一次调用 GetNames(或任何方法)时,我想从数据库中获取数据。我想将结果保存在缓存中,并在第二次调用时使用缓存版本(如果存在)。

谁能举例说明这将如何工作,应该在哪里实施(模型?)以及是否可行。

我已经在传统的 ASP.NET 应用程序中看到过这种情况,通常用于非常静态的数据。

【问题讨论】:

  • 在查看以下答案时,请务必考虑您是否希望您的控制器了解/负责数据访问和缓存问题。通常你想把它分开。请参阅存储库模式以了解这样做的好方法:deviq.com/repository-pattern

标签: asp.net-mvc database caching


【解决方案1】:

这是我使用的一个不错且简单的缓存助手类/服务:

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

缓存提供者会检查缓存中是否有名为“cache id”的内容,如果没有,它将调用委托方法来获取数据并将其存储在缓存中。

示例:

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

【讨论】:

  • 我已经对此进行了调整,以便通过使用 HttpContext.Current.Session 代替每个用户会话使用缓存机制。我还在我的 BaseController 类上放置了一个 Cache 属性,因此它易于访问并更新了构造函数,允许 DI 进行单元测试。希望这会有所帮助。
  • 您还可以将此类和方法设为静态,以便在其他控制器中重用。
  • 这个类不应该依赖于 HttpContext。我在这里简化它只是为了举例。缓存对象必须通过构造函数插入 - 然后可以用其他缓存机制替换。所有这些都是通过 IoC/DI 以及静态(单例)生命周期实现的。
  • @Brendan - 更糟糕的是,它为缓存键设置了魔术字符串,而不是从方法名称和参数中推断出它们。
  • 这是一个很棒的低级解决方案。就像其他人提到的那样,您希望将其包装在一个类型安全的、特定于域的类中。由于魔法字符串,直接在控制器中访问它将是一场维护噩梦。
【解决方案2】:

在您的模型中引用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 特定的,我一直使用这种方法来缓存数据。

【讨论】:

  • 我不推荐这种解决方案:在返回时,您可能会再次获得一个空对象,因为它正在缓存中重新读取,并且可能已经从缓存中删除了。我宁愿这样做: public string[] GetNames() { string[] noms = Cache["names"];如果(标称 == 空){ 标称 = DB.GetNames();缓存[“名称”] = noms; } 返回(标称); }
  • 我同意 Oli.. 从对数据库的实际调用中获取结果比从缓存中获取结果更好
  • 这是否适用于延迟查询的DB.GetNames().AsQueryable 方法?
  • 除非您将返回值从 string[] 更改为 IEnumerable
  • 如果不设置过期..缓存默认什么时候过期?
【解决方案3】:

我指的是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;这行不是放入缓存了吗?
  • @Baddie 是的。但是这个例子与 Oli 所指的第一个不同,因为他不再访问缓存 - 问题在于只是这样做: return (string[])Cache["names"]; .. 可能导致返回空值,因为它可能已过期。这不太可能,但它可能会发生。这个例子更好,因为我们将从db返回的实际值存储在内存中,缓存该值,然后返回该值,而不是从缓存中重新读取的值。
  • 或者...从缓存中重新读取的值,如果它仍然存在(!= null)。因此,整个缓存点。这只是说它会仔细检查空值,并在必要时读取数据库。非常聪明,谢谢 Oli!
  • 您能否分享一些链接,我可以在其中阅读有关基于键值的应用程序缓存的信息。我找不到链接。
  • @Oli , 如何从 CSHTML 或 HTML 页面使用此缓存记录
【解决方案4】:

对于 .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 命名空间包含为 Web 和非 Web 应用程序设计的 API。

更多信息:

【讨论】:

  • Db.GerNames() 来自哪里?
  • DB.GetNames 只是 DAL 中的一个方法,它从数据库中获取一些名称。这是您通常会检索到的任何内容。
  • 这应该在顶部,因为它有当前的相关解决方案
  • 谢谢,还需要添加 System.Runtime.Caching nuget 包(v4.5)。
【解决方案5】:

Steve Smith 写了两篇很棒的博客文章,展示了如何在 ASP.NET MVC 中使用他的 CachedRepository 模式。它有效地使用了存储库模式,让您无需更改现有代码即可获得缓存。

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

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

在这两篇文章中,他向您展示了如何设置此模式,并解释了它为何有用。通过使用这种模式,您可以获得缓存,而您现有的代码不会看到任何缓存逻辑。本质上,您使用缓存的存储库就像使用其他存储库一样。

【讨论】:

【解决方案6】:

我以这种方式使用它,它对我有用。 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;

}

【讨论】:

  • 对具有完整命名空间的完全合格的东西额外投票!!
  • DateTime.Noew 在回答中。小心。
【解决方案7】:

AppFabric 缓存 是一种分布式内存缓存技术,它使用跨多个服务器的物理内存以键值对的形式存储数据。 AppFabric 为 .NET Framework 应用程序提供了性能和可伸缩性改进。 Concepts and Architecture

【讨论】:

  • 这是特定于 Azure,而不是一般的 ASP.NET MVC。
【解决方案8】:

扩展@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

第二个助手特别好,因为大多数数据键不是复合的。如果您经常使用复合键,则可以添加其他方法。通过这种方式,您可以避免执行各种字符串连接或 string.Formats 来获取传递给缓存助手的键。它还使传递数据访问方法更容易,因为您不必将 ID 传递给包装器方法……对于大多数用例来说,整个事情变得非常简洁和一致。

【讨论】:

  • 您的接口定义缺少“durationInMinutes”参数。 ;-)
【解决方案9】:

这是对 Hrvoje Hudo 答案的改进。此实现有几个关键改进:

  • 缓存键是根据更新数据的函数和传入的指定依赖关系的对象自动创建的
  • 为任何缓存持续时间传递时间跨度
  • 使用锁来保证线程安全

请注意,这依赖于 Newtonsoft.Json 来序列化 dependsOn 对象,但可以轻松地将其替换为任何其他序列化方法。

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

【讨论】:

  • if (item == null) 应该在锁内。现在当这个if 在锁之前,就会发生竞争情况。或者更好的是,您应该在锁之前保留if,但重新检查缓存是否仍然是空的,作为锁内的第一行。因为如果两个线程同时到来,它们都会更新缓存。您当前的锁定没有帮助。
【解决方案10】:
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);
    }
}

【讨论】:

  • 考虑添加一些解释
【解决方案11】:

我使用两个类。第一个缓存核心对象:

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

【讨论】:

    【解决方案12】:

    我会说在这个持久数据问题上实施 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
    

    【讨论】:

    • 这对我来说非常有效,这就是为什么我向所有可能会得到帮助的人推荐它
    【解决方案13】:
    HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
    

    【讨论】:

      【解决方案14】:

      您也可以尝试使用 ASP MVC 中内置的缓存:

      将以下属性添加到您要缓存的控制器方法中:

      [OutputCache(Duration=10)]
      

      这种情况下this的ActionResult会被缓存10秒。

      更多关于here

      【讨论】:

      • OutputCache 用于渲染 Action ,问题是关于缓存数据而不是页面。
      • 跑题了但是OutputCache也缓存了数据库数据
      猜你喜欢
      • 2013-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-22
      • 2019-02-13
      • 2017-08-26
      相关资源
      最近更新 更多