简体   繁体   中英

Generic caching in c#

Is there a more efficient way to check if cached data exits, if it does get it and if it doesn't make a call to the api/database and then cache it? It seems really inefficient for me to do code like this over and over.

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

Is something like this possible:

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

and then the GetCachedItems would do the check for the cached items and retrieve accordingly. This seems do able, but its when the cached items dont exist and I have to retrieve the items from the api/database that I dont know if its possible to make generic.

The only solution is a switch statement on the type passed in?

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

Thanks for any help.

My solution to this is to pass in the function that gets the data you require cached as a lambda expression. That way, the cache method can check the cache and the call the delegate only when required. For example:

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

Now you can call the Get function generically like this:

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

You can also do this:

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

And now, you'd need to register your data retrieval functions for each type and then simply use the helper:

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

As pointed out in comments below, this solution can have a problem with the lifespan of dependencies ( _mapRepository ) used to retrieve data. A workaround would be to use this same solution but explicitly passing in the dependecies at the moment of data retrieval:

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

}

Now the use would be slightly different:

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

Do note that this last solution is not type safe. You can pass in a wrongly typed provider to GetCachedItems<T> which is unfortunate.

Why don't you use different caches for maps and playlists? If you do so, you can write one base abstract class and overide only the method that read data from api in each of them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM