Laden...

Cacheing Lösung für eine .net 3.5/mono 2.6.7 anwendung

Erstellt von Tarion vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.619 Views
T
Tarion Themenstarter:in
381 Beiträge seit 2009
vor 10 Jahren
Cacheing Lösung für eine .net 3.5/mono 2.6.7 anwendung

Hi,
ich bin dabei ein Repository Pattern für den Datenzugriff zu implementieren und suche nach einer guten Lösung für das Caching der Daten im Speicher. Ich will dabei ungern das Rad neu erfinden und meinen eigenen Cache implementieren ...

Wozu ratet ihr?

2.207 Beiträge seit 2011
vor 10 Jahren

Hey,

das kommt ganz darauf an, was du willst. Was spricht gegen eine normale Liste deiner Objekte? Bei jedem abprüfen fragst du ab, ob es schon vorhanden ist, wenn ja, nimmst dus, wenn nein, fügst dus hinzu. Achte beim updaten darauf, dass dus erneuerst (oder aus dem Cache schmeisst). Oder ein Dictionary, falls du noch einen Key brauchst.

Anbieten kannst dus mit einer "bool TryGetItem(..., out myItem)-Methode zum Beispiel. Die gibt dir das Item zurück, wenn es da ist, wenn nicht, holt sie es eben über das Repository aus der DB.

Wobei das nur ein Beispiel ist.

Gruss

Coffeebean

16.792 Beiträge seit 2008
vor 10 Jahren

Ich habe in meinem Repository einfach einen Cache der bisher geladenen Objekte.

private ICollection<TEntity> _cache;
2.078 Beiträge seit 2012
vor 10 Jahren

Ich hab mir mal diese Erweiterungsmethoden geschrieben:

        public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
        {
            return GetOrDefault(source, key, k => default(TValue));
        }

        public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, Func<TKey, TValue> getDefault)
        {
            if (!source.ContainsKey(key))
                source.Add(key, getDefault(key));

            return source[key];
        }

        public static void SetOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value)
        {
            if (source.ContainsKey(key))
                source[key] = value;
            else
                source.Add(key, value);
        }

Die Methoden müssten doch eigentlich das Gewollte erledigen, oder?
Natürlich nur mit einem Dictionary, aber das für eine Liste umzusetzen ist auch nicht weiter schwer, fordert aber, dass in den jeweiligen Klassen die Equals-Methode passend überschrieben wurde, sonst kann ja nicht sinnvoll in der Liste nach einem vorhandenen Element gesucht werden.

742 Beiträge seit 2005
vor 10 Jahren

Eine Liste ist keine besonders clevere Idee. Wenn du viele Einträge im Cache hast, killt das deine Performance. Damit meine ich nicht, dass es ein bisschen langsamer wird, sondern dass evtl. 30% der Zeit damit draufgeht nur deine Liste zu durchsuchen.

Ich würde die folgende Alternativen in absteigender Reihenfolge empfehlen:

  1. Schaue, ob es für dein ORM Mapper (falls du einen verwendest) eine Level 2-Cache Implementierung gibt. Gibt es afaik für NHibernate und EF, für andere weiß ich es nicht.

  2. Benutze die Caching Klassen aus der Enterprise Library.

  3. Verwende Reflector und kopiere dir ConcurrentDictionary aus .NET 4.0 Assemblies raus.

  4. Erstelle einen eigenene Cache. Verwende mehrere Dictionaries und teile die Daten auf die Dictionaries auf, z.B. int dictionaryIndex = key.GetHashCode() % 10. Die Folge: Weniger Warten durch Locks.

  5. Verwende Dictionaries mit Locking.

16.792 Beiträge seit 2008
vor 10 Jahren

Deine Argumentation funktioniert soweit, sofern Du ein eindeutiges Feld / Eigenschaft hast, die den Key repräsentiert.
Hast Du dies nicht oder sind es mehrere, funktioniert das Dictionary so in dieser Form nicht mehr.

Diese fertigen Libs müssen leider alles beachten, damit sie überall nutzbar sind.
Performanter wird immer eine Lösung sein, die auf generische Verfahren und Reflection verzichtet.

742 Beiträge seit 2005
vor 10 Jahren

Das du überhaupt keinen eindeutigen Key bilden kannst ist wohl die absolute Ausnahme. IN 99,9% der Fälle gibt es eine eindeutige ID oder eine Kombination, die sich nicht ändert.

Bei einem Feld ist die Verwendung eines Dictionaries trivial, man kann in diesem Fall einfach die Id direkt als Key verwenden


Dictionary<Guid, User> usersCache = new Dictionary<Guid, User>();

Falls es mehrere Ids gibt, kann man zusammengesetze Ids verwenden, z.B. Tuple


Dictionary<Tuple<string, int>, User> usersCache = new Dictionary<Tuple<string, int>();

Oder man schreibt einen eigenen Key und implementiert Equals und GetHashCode;


Dictionar<UserId, User> usersCache = new Dictionar<UserId, User>();

Im Fall von normalen Cache-Implementierungen, die üblicherweise Strings verwenden, ist der Ansatz analog, man muss halt den String selber zusammensetzen. Aus Erfahrung würde ich jetzt die These aufstellen, dass Dictionaries und normale Caches nahezu immer funktionieren. Die Ausnahmen müssen dann konkret diskutiert werden. Reflection kommt hier gar nicht zum Einsatz.

16.792 Beiträge seit 2008
vor 10 Jahren

Beim Tuple bzw. mehreren Keys auf diese Weise würde man aber einen Referenzvergleich durchführen; bringt also in diesem Falle nichts.
Bleibt wieder nur der Weg die Einzelwerte zu vergleichen: Einsparung 0.

Dicts als Cache sind also nur dann Sinnvoll, wenn es nur einen Key gibt.
Alles andre ist mit ISet (HashSet) performanter.

742 Beiträge seit 2005
vor 10 Jahren

Das ist jetzt bald Offtopic, aber das stimmt so nicht. Dictionaries verwenden EqualityComparer und die bauen auf Equals auf.

http://msdn.microsoft.com/en-us/library/ms224763%28v=vs.110%29.aspx

Ist Equals NICHT überschrieben, wird ein Referenzvergleich dürchgeführt. Das ist aber bei Tuple nicht der Fall und bei einer UserId auch nicht. Das ist mehr oder weniger auch die gleiche Implementierung wie HashSet.

Mehr oder wengier ist Dictionary<User, User> in der Größenordnung gleich performant wie HashSet<User>() (mit dem gleichen Equality-Comparer).


Dictionary<Tuple<string, int>, int> dictionary = new Dictionary<Tuple<string, int>, int>();

dictionary.Add(new Tuple<string,int>("Test", 1), 2);

Console.WriteLine(dictionary.ContainsKey(new Tuple<string, int>("Test", 1)));
Console.WriteLine(dictionary.ContainsKey(new Tuple<string, int>("Test", 1)));

2.078 Beiträge seit 2012
vor 10 Jahren

Darf ich mich mal dazwischen drängeln und fragen, warum generisches Verhalten langsamer ist?

Das Reflection langsam ist, weiß ich, aber bei Generika hab ich das noch nicht gehört.

742 Beiträge seit 2005
vor 10 Jahren

Wüsste ich auch gerne. Meines Wissens nach sind Generics eher schneller, weil auf Boxing/Unboxing verzichtet werden kann. In vielen Fällen kann man aber natürlich für ein spezielles Problem immer eine performantere Lösung finden, aber man sollte sich nicht dazu verleiten lassen, Kleinkram zu optimieren. Das lohnt sich meistens nicht, verursacht mehr Code und potentielle Fehler und machen oft auch den Code schwerer zu warten.

Btw: Wo wir gerade bei Performance sind: In machen Fällen kann man anstatt Reflection sich auch zur Initialisierungszeit schnelle Zugriffsklassen automatisch generieren lassen und laden. Das wird beispielweise vom XmlSerializer und auch von ORM-Mappern gemacht. Das ist wirklich sehr schnell und relativ elegant, wie ich finde.

16.792 Beiträge seit 2008
vor 10 Jahren

Da hab ihr mich jetzt falsch verstanden; vllt war da mein Ausdruck nicht optimal.
Generica an für sich sind natürlich nicht langsamer wenn wir jetzt von <T> sprechen.

Sondern sind meiner Erfahrung nach die "allgemeinen Lösungen" = generische Lösungen wie etwa das Enterprise-Verfahren langsamer, als eine auf sich spezialisierte Lösung.