Как я могу кэшировать объекты в ASP.NET MVC?
Я хотел бы кэшировать объекты в ASP.NET MVC. У меня есть BaseController
, который я хочу, чтобы все контроллеры наследовали. В BaseController есть свойство User
, которое просто захватывает данные пользователя из базы данных, чтобы я мог использовать его в контроллере или передавать его в представления.
Я хотел бы кэшировать эту информацию. Я использую эту информацию на каждой странице, поэтому нет необходимости обращаться к каждой странице с запросом каждой страницы.
Мне хотелось бы что-то вроде:
if(_user is null)
GrabFromDatabase
StuffIntoCache
return CachedObject as User
Как реализовать простое кэширование в ASP.NET MVC?
Ответы
Ответ 1
Вы все равно можете использовать кеш (общий для всех ответов) и сеанс (уникальный для каждого пользователя) для хранения.
Мне нравится следующий шаблон "try get from cache/create and store" (С# -подобный псевдокод):
public static class CacheExtensions
{
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
{
var result = cache[key];
if(result == null)
{
result = generator();
cache[key] = result;
}
return (T)result;
}
}
вы должны использовать это так:
var user = HttpRuntime
.Cache
.GetOrStore<User>(
$"User{_userId}",
() => Repository.GetUser(_userId));
Вы можете адаптировать этот шаблон к сеансу, ViewState (ugh) или к любому другому механизму кэширования. Вы также можете расширить ControllerContext.HttpContext(который, я думаю, является одним из оберток в System.Web.Extensions), или создать новый класс, чтобы сделать это с некоторой комнатой для насмешивания кеша.
Ответ 2
Я взял "Ответ" и изменил его, чтобы сделать класс CacheExtensions
статическим и предложить небольшое изменение, чтобы иметь возможность Func<T>
быть null
:
public static class CacheExtensions
{
private static object sync = new object();
public const int DefaultCacheExpiration = 20;
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="cache">calling object</param>
/// <param name="key">Cache key</param>
/// <param name="generator">Func that returns the object to store in cache</param>
/// <returns></returns>
/// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator ) {
return cache.GetOrStore( key, (cache[key] == null && generator != null) ? generator() : default( T ), DefaultCacheExpiration );
}
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="cache">calling object</param>
/// <param name="key">Cache key</param>
/// <param name="generator">Func that returns the object to store in cache</param>
/// <param name="expireInMinutes">Time to expire cache in minutes</param>
/// <returns></returns>
public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator, double expireInMinutes ) {
return cache.GetOrStore( key, (cache[key] == null && generator != null) ? generator() : default( T ), expireInMinutes );
}
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="cache">calling object</param>
/// <param name="key">Cache key</param>
/// <param name="obj">Object to store in cache</param>
/// <returns></returns>
/// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
public static T GetOrStore<T>( this Cache cache, string key, T obj ) {
return cache.GetOrStore( key, obj, DefaultCacheExpiration );
}
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="cache">calling object</param>
/// <param name="key">Cache key</param>
/// <param name="obj">Object to store in cache</param>
/// <param name="expireInMinutes">Time to expire cache in minutes</param>
/// <returns></returns>
public static T GetOrStore<T>( this Cache cache, string key, T obj, double expireInMinutes ) {
var result = cache[key];
if ( result == null ) {
lock ( sync ) {
result = cache[key];
if ( result == null ) {
result = obj != null ? obj : default( T );
cache.Insert( key, result, null, DateTime.Now.AddMinutes( expireInMinutes ), Cache.NoSlidingExpiration );
}
}
}
return (T)result;
}
}
Я также рассмотрел бы этот шаг еще раз для реализации тестируемого решения сеанса, которое расширяет абстрактный класс System.Web.HttpSessionStateBase.
public static class SessionExtension
{
/// <summary>
///
/// </summary>
/// <example><![CDATA[
/// var user = HttpContext
/// .Session
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="generator"></param>
/// <returns></returns>
public static T GetOrStore<T>( this HttpSessionStateBase session, string name, Func<T> generator ) {
var result = session[name];
if ( result != null )
return (T)result;
result = generator != null ? generator() : default( T );
session.Add( name, result );
return (T)result;
}
}
Ответ 3
Если вы хотите, чтобы он был кэширован для длины запроса, поместите это в свой базовый класс контроллера:
public User User {
get {
User _user = ControllerContext.HttpContext.Items["user"] as User;
if (_user == null) {
_user = _repository.Get<User>(id);
ControllerContext.HttpContext.Items["user"] = _user;
}
return _user;
}
}
Если вы хотите кешировать дольше, используйте замену вызова ControllerContext с одним на Cache []. Если вы решите использовать объект Cache для кэширования дольше, вам нужно будет использовать уникальный кеш-ключ, поскольку он будет использоваться для всех пользователей/пользователей.
Ответ 4
Мне нравится скрывать тот факт, что данные кэшируются в репозитории. Вы можете получить доступ к кешу через свойство HttpContext.Current.Cache и сохранить информацию пользователя с помощью "User" + id.ToString() в качестве ключа.
Это означает, что весь доступ к данным пользователя из репозитория будет использовать кешированные данные, если они доступны, и не требует изменений кода в модели, контроллере или представлении.
Я использовал этот метод для исправления серьезных проблем с производительностью в системе, которая запрашивала базу данных для каждого свойства пользователя и уменьшала время загрузки страницы с минут на одну цифру секунд.
Ответ 5
@njappboy: Хорошая реализация. Я бы отложил вызов Generator( )
до последнего ответственного момента. таким образом, вы также можете кэшировать вызовы методов.
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <returns></returns>
/// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator )
{
return Cache.GetOrStore( Key, Generator, DefaultCacheExpiration );
}
/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
/// .Cache
/// .GetOrStore<User>(
/// string.Format("User{0}", _userId),
/// () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <param name="ExpireInMinutes">Time to expire cache in minutes</param>
/// <returns></returns>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator, double ExpireInMinutes )
{
var Result = Cache [ Key ];
if( Result == null )
{
lock( Sync )
{
if( Result == null )
{
Result = Generator( );
Cache.Insert( Key, Result, null, DateTime.Now.AddMinutes( ExpireInMinutes ), Cache.NoSlidingExpiration );
}
}
}
return ( T ) Result;
}
Ответ 6
Если вам не нужны специальные функции недействительности кэширования ASP.NET, статические поля довольно хороши, легки и просты в использовании. Однако, как только вам нужны расширенные функции, вы можете переключиться на объект ASP.NET Cache
для хранения.
Подход, который я использую, заключается в создании свойства и поля private
. Если поле null
, свойство заполнит его и вернет. Я также предоставляю метод InvalidateCache
, который вручную устанавливает поле в null
. Преимущество такого подхода заключается в том, что механизм кэширования инкапсулирован в свойство, и вы можете переключиться на другой подход, если хотите.
Ответ 7
Реализация с минимальной блокировкой кеша. Значение, хранящееся в кеше, заверяется в контейнер. Если значение не находится в кеше, контейнер значений заблокирован. Кэш заблокирован только во время создания контейнера.
public static class CacheExtensions
{
private static object sync = new object();
private class Container<T>
{
public T Value;
}
public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, TimeSpan slidingExpiration)
{
return cache.GetOrStore(key, create, Cache.NoAbsoluteExpiration, slidingExpiration);
}
public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration)
{
return cache.GetOrStore(key, create, absoluteExpiration, Cache.NoSlidingExpiration);
}
public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
{
return cache.GetOrCreate(key, x => create());
}
public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<string, TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
{
var instance = cache.GetOrStoreContainer<TValue>(key, absoluteExpiration, slidingExpiration);
if (instance.Value == null)
lock (instance)
if (instance.Value == null)
instance.Value = create(key);
return instance.Value;
}
private static Container<TValue> GetOrStoreContainer<TValue>(this Cache cache, string key, DateTime absoluteExpiration, TimeSpan slidingExpiration)
{
var instance = cache[key];
if (instance == null)
lock (cache)
{
instance = cache[key];
if (instance == null)
{
instance = new Container<TValue>();
cache.Add(key, instance, null, absoluteExpiration, slidingExpiration, CacheItemPriority.Default, null);
}
}
return (Container<TValue>)instance;
}
}
Ответ 8
Несколько других ответов здесь не касаются следующего:
- cache stampede
- блокировка двойной проверки
Это может привести к тому, что генератор (который может занять много времени) работает более одного раза в разных потоках.
Здесь моя версия, которая не должна страдать от этой проблемы:
// using System.Web.Caching;
public static class CacheExtensions
{
private static object sync = new object();
private static TimeSpan defaultExpire = TimeSpan.FromMinutes(20);
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator) =>
cache.GetOrStore(key, generator, defaultExpire);
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator, TimeSpan expire)
{
var result = cache[key];
if (result == null)
{
lock (sync)
{
result = cache[key];
if (result == null)
{
result = generator();
cache.Insert(key, result, null, DateTime.Now.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
}
}
}
return (T)result;
}
}