2017-02-01 3 views
2

Есть ли более эффективный способ проверки того, выходит ли кешированные данные, если он его получает, и если он не делает вызов в api/database, а затем кеширует его? Мне кажется очень неэффективным, чтобы я делал такой код снова и снова.Общее кэширование в C#

List<Map> maps = new List<Map>(); 
List<Playlist> playlists = new List<Playlist>(); 

if (SingletonCacheManager.Instance.Get<List<Map>>("Maps") != null) 
{ 
    maps = SingletonCacheManager.Instance.Get<ListMap>>("Maps"); 
} 
else 
{ 
    maps = _mapRepository.FindBy(x => x.Active).ToList(); 
    SingletonCacheManager.Instance.Add<List<Map>>(maps, "Maps", 20); 
} 

if (SingletonCacheManager.Instance.Get<List<Playlist>>("Playlists") != null) 
{ 
    playlists = SingletonCacheManager.Instance.Get<List<Playlist>>("Playlists"); 
} 
else 
{ 
    var p = await _apiService.GetPlaylists(); 
    playlists = p.ToList(); 
    SingletonCacheManager.Instance.Add<List<Playlist>>(playlists, "Playlists", 20); 
} 

Это что-то вроде этого можно:

List<Map> maps = this.CacheHelper.GetCachedItems<Map>(key, lengthoftime); 

, а затем GetCachedItems будет делать проверку кэшированных элементов и получить соответственно. Это похоже на способ, но его, когда кэшированные элементы не существуют, и я должен получить элементы из api/database, которые я не знаю, возможно ли сделать их общий.

Единственное решение - это оператор switch на тип переданного?

switch(<T>type) 
{ 
    case<Map>: 
     return _mapRepository.FindBy(x => x.Active); 
    case<Playlist>: 
     return await _apiService.GetPlaylists(); 
} 

Спасибо за любую помощь.

+1

Я делаю что-то вроде этого: https://github.com/WiredUK/Wired.Caching/blob/master/Wired.Caching/InMemoryCache.cs#L52. Поэтому по существу я передаю функцию, чтобы получить данные (из базы данных, API или что-то еще) в функцию кеша и, если требуется, будет вызван только вызов. – DavidG

ответ

1

Моим решением является передача функции, которая получает данные, которые вы должны кэшировать как выражение лямбда. Таким образом, метод кеша может проверять кеш и вызов делегата только тогда, когда это необходимо. Например:

public T Get<T>(string key, Func<T> getItemDelegate, int duration) where T : class 
{ 
    var cache = GetCache(); 

    var item = SingletonCacheManager.Instance.Get<ListMap>>(key) as T; 

    if (item != null) return item; 

    item = getItemDelegate(); 

    SingletonCacheManager.Instance.Add<T>(item, key, duration); 

    return item; 
} 

Теперь вы можете вызвать функцию Get обобщенно, как это:

var maps = Get<List<Map>>(
    "Maps", 
    () => _mapRepository.FindBy(x => x.Active).ToList(), 
    20); 
+0

Это крутой ответ! Я пытаюсь реализовать, когда я вернусь домой. –

+0

Возможно, вы также должны добавить «блокировку» этого кода. Предпочтительно, основываясь на ключе, но вы можете заблокировать общий объект, хотя это может повлиять на вашу производительность. – DavidG

+0

Не понял, что вы сделали комментарий в OP. Я думаю, что это именно то, что я ищу! Спасибо тебе за это. –

0

Почему вы не используете разные кеши для карт и плейлистов? Если вы это сделаете, вы можете написать один базовый абстрактный класс и переопределить только метод, который считывает данные из api в каждом из них.

+0

Интересная идея, может дать вам понять, как она себя чувствует –

+0

Ваша идея с коммутатором не будет работать, потому что вы не можете использовать там тип. –

+0

Да, это было просто что-то, что было на моей голове. –

1

Вы также можете сделать это:

public interface ICacheManager 
{ 
    IList<T> Get<T>(string name); 
    void Add<T>(IList<T> data, string Id, int lifeTime); 
} 

public class CacheHelper 
{ 
    private readonly Dictionary<Tuple<Type, string>, Func<IEnumerable<object>>> dataRetrievalFuncs; 
    private readonly ICacheManager cacheManager; 

    public CacheHelper(ICacheManager cacheManager) 
    { 
     this.cacheManager = cacheManager; 
     dataRetrievalFuncs = new Dictionary<Tuple<Type, string>, Func<IEnumerable<object>>>(); 
    } 

    public void Register<T>(string name, Func<IEnumerable<T>> selector) where T : class 
    { 
     dataRetrievalFuncs[new Tuple<Type, string>(typeof(T), name)] = 
      () => (IEnumerable<object>)selector(); 
    } 

    public IList<T> GetCachedItems<T>(string name, int lifeTime = 20) 
     where T : class 
    { 
     var data = cacheManager?.Get<T>(name); 

     if (data == null) 
     { 
      data = (dataRetrievalFuncs[new Tuple<Type, string>(
         typeof(T), name)]() as IEnumerable<T>) 
        .ToList(); 
      cacheManager.Add(data, name, lifeTime); 
     } 

     return data; 
    } 
} 

И теперь, вы должны зарегистрироваться ваши функции поиска данных для каждого типа, а затем просто используйте хелпер:

//Setting up the helper 
CacheHelper helper = new CacheHelper(SingletonCacheManager.Instance); 
helper.Register("Maps",() => _mapRepository.FindBy(x => x.Active)); 
helper.Register("PlayLists", ...); 

//Retrieving data (where it comes from is not your concern) 
helper.GetCachedItems<Map>("Maps"); 
helper.GetCachedItems<PlayList>("Playlists"); 

Как указано в комментариях ниже, это решение может иметь проблему с продолжительностью зависимостей (_mapRepository), используемой для извлечения данных. Обходной бы использовать этот же раствор, но явно передавая в dependecies в момент извлечения данных:

public class CacheHelper 
{ 
    private readonly Dictionary<Tuple<Type, string>, Func<object, IEnumerable<object>>> dataRetrievalFuncs; 
    private readonly ICacheManager cacheManager; 

    public CacheHelper(ICacheManager cacheManager) 
    { 
     this.cacheManager = cacheManager; 
     dataRetrievalFuncs = new Dictionary<Tuple<Type, string>, Func<object, IEnumerable<object>>>(); 
    } 

    public void Register<TEntity, TProvider>(string name, Func<TProvider, IEnumerable<TEntity>> selector) 
     where TEntity : class 
     where TProvider: class 
    { 
     dataRetrievalFuncs[new Tuple<Type, string>(typeof(TEntity), name)] = 
      provider => (IEnumerable<object>)selector((TProvider)provider) 
    } 

    public IList<TEntity> GetCachedItems<TEntity>(string name, object provider, int lifeTime = 20) 
     where TEntity : class 
    { 
     var data = cacheManager?.Get<TEntity>(name); 

     if (data == null) 
     { 
      data = (dataRetrievalFuncs[new Tuple<Type, string>( 
         typeof(TEntity), name)](provider) as IEnumerable<TEntity>) 
        .ToList(); 
      cacheManager?.Add(data, name, lifeTime); 
     } 

     return data; 
    } 

} 

Теперь использование будет немного отличаться:

//Setting up the helper 
CacheHelper helper = new CacheHelper(SingletonCacheManager.Instance); 
helper.Register("Maps", (MapRepository r) => r.FindBy(x => x.Active)); 

//Retrieving data (where it comes from is not your concern) 
helper.GetCachedItems<Map>("Maps", _mapRepository); 

Обратите внимание, что это последнее решение не является безопасным. Вы можете ошибочно ввести provider в GetCachedItems<T>, что является неудачным.

+0

Это сломается, если у вас есть два кэша одного типа. Используя пример OPs, он получает активные карты как «Список <Карта». что, если они хотят, чтобы неактивные карты тоже кэшировались? – DavidG

+0

@DavidG Правда, но это легко разрешимо; 'Tuple ' может использоваться как ключ к словарю, а не только 'Type'. Я добавлю это к ответу, это хороший момент. – InBetween

+0

Действительно. Конечно, еще одна проблема заключается в том, что зависимости, требуемые для функций, могут больше не существовать. Например, контекст db может быть закрыт/удален. – DavidG