2017-02-01 3 views
2

Gibt es eine effizientere Möglichkeit zu überprüfen, ob zwischengespeicherte Daten existieren, ob sie es erhält und ob sie die api/database nicht aufruft und dann zwischenspeichert? Es scheint wirklich ineffizient für mich, Code wie diesen immer wieder zu machen.Generisches Caching in 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); 
} 

ist so etwas wie diese möglich:

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

und dann das GetCachedItems würde die Prüfung für die zwischengespeicherten Elemente tun und entsprechend abgerufen werden. Dies scheint möglich, aber es ist, wenn die zwischengespeicherten Elemente nicht vorhanden sind und ich die Elemente aus der API/Datenbank abrufen muss, die ich nicht weiß, ob es möglich ist, generisch zu machen.

Die einzige Lösung ist eine switch-Anweisung des übergebenen Typs?

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

Danke für jede Hilfe.

+1

Ich mache etwas wie folgt: https://github.com/WiredUK/Wired.Caching/blob/master/Wired.Caching/InMemoryCache.cs#L52. Im Wesentlichen übergebe ich die Funktion, um die Daten (von der Datenbank, API oder was auch immer) in die Cache-Funktion zu bekommen und die nur bei Bedarf aufgerufen wird. – DavidG

Antwort

1

Meine Lösung ist, die Funktion übergeben, die die Daten erhält, die Sie als Lambda-Ausdruck zwischengespeichert. Auf diese Weise kann die Cache-Methode den Cache überprüfen und den Delegaten nur bei Bedarf aufrufen. Zum Beispiel:

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

Jetzt können Sie die Get-Funktion wie diese allgemein nennen:

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

Das ist eine coole Antwort! Ich werde es versuchen und umsetzen, wenn ich nach Hause komme. –

+0

Sie sollten wahrscheinlich auch eine "Sperre" um diesen Code hinzufügen. Vorzugsweise basierend auf dem Schlüssel, aber Sie könnten ein allgemeines Objekt sperren, das jedoch Ihre Leistung beeinträchtigen könnte. – DavidG

+0

Habe nicht bemerkt, dass Sie den Kommentar im OP gemacht haben. Ich denke, das ist genau das, wonach ich suche! Danke dafür. –

0

Warum verwenden Sie nicht verschiedene Caches für Karten und Playlists? Wenn Sie dies tun, können Sie eine abstrakte Basisklasse schreiben und nur die Methode überschreiben, die in jedem von ihnen Daten aus api liest.

+0

Interessante Idee, könnte dies ausprobieren und sehen, wie es sich anfühlt –

+0

Ihre Idee mit Schalter wird nicht funktionieren, weil Sie Typ dort nicht verwenden können. –

+0

Ja, es war einfach etwas, was mir in den Sinn kam. –

1

Sie auch dies tun können:

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

Und jetzt, würden Sie sich registrieren müssen Ihre Datenabfragefunktionen für jeden Typ und dann einfach den Helfer:

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

Wie in den Kommentaren unten erwähnt, kann diese Lösung ein Problem mit der Lebensdauer von Abhängigkeiten (_mapRepository) haben, die zum Abrufen von Daten verwendet werden. Eine Abhilfe wäre, diese gleiche Lösung zu verwenden, aber in dem dependecies zum Zeitpunkt der Datenabfrage explizit vorbei:

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

} 

Nun ist die Verwendung wäre etwas anders:

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

Sie beachten, dass diese letzte Lösung ist nicht typsicher. Sie können eine falsch eingegebene provider zu GetCachedItems<T> übergeben, was bedauerlich ist.

+0

Dies wird abgebrochen, wenn Sie zwei Caches vom selben Typ haben. Unter Verwendung von OPs erhält er aktive Karten als "Liste DavidG

+0

@DavidG Wahr, aber das ist leicht lösbar; Ein 'Tupel ' könnte als Schlüssel für das Wörterbuch verwendet werden und nicht nur für 'Typ'. Ich füge das zur Antwort hinzu, es ist ein guter Punkt. – InBetween

+0

In der Tat. Ein weiteres Problem ist natürlich, dass Abhängigkeiten, die für die Funktionen benötigt werden, nicht mehr existieren. Zum Beispiel könnte ein db-Kontext geschlossen/entsorgt worden sein. – DavidG