Statisches Caching
Framework Studio stellt einen statischen Caching-Mechanismus bereit. Wird dieser für eine geeignete Component aktiviert, so können Daten statt von der Datenbank aus diesem Cache geladen werden. Dadurch sind erhebliche Performance-Vorteile möglich, da die Anzahl der Datenbankzugriffe reduziert wird.
Anwendungsgebiete
In Geschäftsanwendungen gibt es viele kleine Datenbanktabellen mit wenigen Spalten und verhältnismäßig wenigen Datensätzen. Häufig enthalten diese Tabellen Daten, welche im täglichen Betrieb kaum Veränderungen unterliegen. Beispiele sind Tabellen zu Währungen, Sprachen, Versandarten, Lagerorten, Steuerschlüsseln, um nur einige zu nennen.
Für diese überschaubar kleinen Datenmengen mit sich eher selten ändernden Daten ist das statische Caching geeignet.
Konzept
Der Entwickler kann definieren, dass für eine Component ein statischer Cache bereit gestellt werden soll.
Der statische Cache läd bei Bedarf die benötigten "Rohdaten" (soweit möglich sessionübergreifend, also benutzerunabhängig) pro .NET-Prozess ("w3wp.exe") in den Arbeitsspeicher. Wird eine Anfrage an den Cache gestellt und die Daten im Cache sind älter als 5 Minuten, so werden die Daten neu von der Datenbank in den Cache gelesen. Bei der Anfrage an den Cache werden die Rohdaten benutzerabhängig gefiltert und anschließend Objekte daraus erzeugt.
Werden Components gespeichert, so liefert der Cache für den speichernden Benutzer die geänderten Daten, für alle anderen Benutzer weiterhin die ungeänderten Daten. Wird nach dem Speichern ein Commit ausgeführt, wird der Cache automatisch invalidiert, sodass der nächste lesende Zugriff auf den Cache (egal durch welchen Benutzer in diesem .NET-Prozess) dafür sorgt, dass die Daten neu von der Datenbank eingelesen werden.
Note
Der Cache ist streng typisiert. Where-Bedingungen müssen als Function formuliert werden. Die Angabe eines Where-Strings ist nicht möglich.
Der Cache berücksichtigt die über die Virtual Columns an der Component definierte Sortierung. Besonders Effizient können Daten über den (an der DBTable definierten) logischen Primärschlüssel (PK) abgefragt werden.
Important
Zu beachten ist, dass der lesende Zugriff auf den statischen Cache (analog zum "normalen" Laden von der Datenbank), für ein Global nicht thread-save ist.
Nutzung
Aktivieren des Caches
Ist eine Component für das statische Caching geeignet, weil sie z.B.
- wenige hundert Datensätze verwaltet
- die Datensätze kaum 20 Eigenschaften enthalten, die alle nicht große Datenmengen (wie z.B. Byte-Arrays für Bilder) enthalten
- die Anzahl der Änderungen pro Tag an zumindest einem Datensatz an den Fingern abzuzählen sind
- es (bis auf einzelne, spezielle Ausnahmen) völlig ausreichend ist, dass die Daten bis zu 5 Minuten alt sein können
dann kann der Entwickler durch setzen der Checkbox Provide Static Cache auf der Registerkarte General im Designerfenster der Component dafür sorgen, dass für diese Component ein statischer Cache generiert wird.
Die Checkbox ist generell in jedem bearbeitbaren Package aktivierbar, soweit sich die Package-Version nicht im Service-Release Modus befindet.
Wurde die Checkbox in einem Basis-Package gesetzt, kann diese Einstellung in Custom-Packages nicht mehr rückgängig gemacht werden.
Nicht gesetzt werden kann die Checkbox, wenn die Component keinen Datenbankbezug hat.
Zugriff auf den Cache
Wurde für eine Component der statische Cache aktiviert, generiert Framework Studio für die Component eine statische Klasse aus dem Namen der Component gefolgt vom Suffix "Cache" in die Interface-Dll.
Beispiel:
Für die Component cdCurrency
wird eine Klasse cdCurrencyCache
generiert.
Diese Klasse enthält eine statische Methode Get(IGlobalObjects global)
, welche eine generische Singleton-Instanz zum Zugriff auf den statischen Cache mit dem übergebenen Global zurück gibt. Diese Instanz implementiert das Interface IStaticCache<TObj, TPK>, wobei TObj
der Interface-Typ der Component ist (z.B. IcdCurrency) und PK
der Typ des/der Primärschlüssel-Properties ist (z.B. FSshort für shtCurrencyID).
Das Ermitteln eines Objektes anhand des Primärschlüssels ist dann beispielsweise über den Indexer möglich:
IcdCurrency oCurrency = cdCurrencyCache.Get(this.Global)[shtCurrencyIDP];
Sollte der Primärschlüssel aus mehreren Properties bestehen, so ist ein ValueTuple mit allen Properties des Primärschlüssels in alphabetischer Reihenfolge anzugeben.
Beispiel (Primärschlüssel sType und sCode):
var oDemo = cdDemoCache.Get(this.Global)[(sMyCode, sMyType)];
Der Cache implementiert IEnumerable<TObj>
. Der folgende Code
IcdCurrencyColl oCurrencyColl = cdCurrencyCollFactory.Create(this.Global);
oCurrencyColl.Load("COALESCE([shtBlocked], 0) = 0", "shtCurrencyCode");
foreach (IcdCurrency oCurrency in oCurrencyColl)
{
// ... do something
}
lässt sich daher wie folgt auf die Verwendung des Caches umstellen.
foreach (var oCurrency in cdCurrencyCache.Get(this.Global)
.Where(c => c.shtBlocked.Value == 0)
.OrderBy(c => c.shtCurrencyCode))
{
// ... do something
}
Der Cache bietet darüberhinaus viele weitere Methoden, um Collections oder Objekte zu befüllen.
Note
Die detaillierte Beschreibung aller Funktionen ist bei der Beschreibung des Interfaces IStaticCache<TObj, TPK> einzusehen.
Sollte es einmal nötig sein, auf Ebene von IDevFrameworkDataObject
auf einen Cache zuzugreifen, so bietet die statische Klasse StaticCache ebenfalls eine statische Methode Get(IGlobalObjects global)
an, mit der (soweit existent) ein untypisierter Cache (IStaticCache) ermittelt werden kann.
Relation-Properties
An Components können Properties vom Typ Relation angelegt werden. Typischerweise werden als DependsOn Properties die Primärschlüsseleigenschaften angegeben.
Relation-Properties verwenden automatisch den Cache, wenn für den Property-Typ der statische Cache aktiviert ist und die DependsOn-Bedingung dem am DBTable definierten logischen Primärschlüssel entspricht.
Globale Einsprungpunkte
Grundidee des statischen Caches ist, alle Daten sessionübergreifend, also benutzerunabhängig, pro .NET-Prozess ("w3wp.exe") in den Arbeitsspeicher zu laden. Dadurch kann der Cache nicht den globalen Einsprungpunkt public virtual void GlobalOnBeforeLoad(DevFrameworkObject obj, ref string loadCondition, ref string replacingHavingClause, ref string replacingOrderClause) durchlaufen, da hier pro Session anhand eines beliebigen Where-Strings unterschiedlich gefiltert werden kann.
Eine einfache Filterung ist für den Cache jedoch in dem Moment möglich, in dem Objekte (zu einem konkreten Global) aus dem Cache angefragt werden. Dazu wurde der globale Einsprungpunkt protected internal virtual bool GlobalWhereStaticCache(IStaticCacheObject oStaticCacheObject) geschaffen, welcher eine Filterung der Ergebnismenge durch den Cache ermöglicht.
Der zuletzt genannte Einsprungpunkt ist jedoch nicht in der Lage, beliebiges SQL im Nachhinein auszuwerten. Sollte jedoch eine Filterung nötig sein, die direkt beim Laden komplexes SQL ermöglicht, kann der statische Cache die Daten nicht sessionübergreifend einlesen. Ebenso ist es in diesem Einsprungpunkt nicht möglich, Einschränkungen auf Werte vorzunehmen, welche zwar als Tabellenspalte existieren, jedoch nicht als Property an der Component angelegt sind.
Deshalb wurde ein weiterer globaler Einsprungpunkt protected internal virtual StaticCacheMode GlobalGetStaticCacheMode(IDevFrameworkDataObject oDevFrameworkDataObject) geschaffen, welcher pro Component einmalig durchlaufen wird um festzulegen, in welchem Modus der Cache arbeiten soll.
Die beiden Modi Broker (default) und Session sowie ihre Konsequenzen sind im Detail am StaticCacheMode beschrieben.
Im Modus Session läd der statische Cache die "Rohdaten" nicht global sondern pro Session. Dadurch erhöht sich der Arbeitspeicherverbrauch. Gleichzeitig kann so der erstgenannte Einsprungpunkt public virtual void GlobalOnBeforeLoad(DevFrameworkObject obj, ref string loadCondition, ref string replacingHavingClause, ref string replacingOrderClause) wie gewohnt durchlaufen werden.
Besonderheiten
Distinct und GroupBy
Important
Ist eine Component als Distinct oder GroupBy definiert, darf der statische Cache zu dieser Component nicht im Modus Broker betrieben werden.
Wenn eine Component nicht als Distinct oder GroupBy definiert ist, in einem speziellen Fall jedoch Distinct oder GroupBy gewünscht ist, kann an den Methoden des statischen Caches, die die Angabe eines orderBy ermöglichen, als orderBy die entsprechende Funktion angegeben werden.
Beispiel Distinct:
public virtual int LoadDistinctByName()
{
return cdArticleCache.Get(this.Global).FillCollection(This, l => l.DistinctBy(o => o.sName));
}
Beispiel GroupBy:
public virtual int LoadGroupByName()
{
// Gruppiert werden soll nach sName
Func<IcdArticle, FSstring> fnKeySelector = oArticle => oArticle.sName;
// Ergebnisobjekt vom Typ IcdArticle zu jedem Schlüsselwert und den dazugehörigen Artikeln erzeugen
Func<FSstring, IEnumerable<IcdArticle>, IcdArticle> fnResultSelector = (sName, oArticles) =>
{
IcdArticle oResult = cdArticleFactory.Create(this.Global);
var oFirstArticle = oArticles.First();
oResult.sID = oFirstArticle.sID;
oResult.sName = oFirstArticle.sName;
oResult.sColor = oFirstArticle.sColor;
// Hinweis: Auch wenn mehrere Aggregatfunktionen (Sum, Count, Max, ...) aufgerufen werden, sollte nur einmal über alle Elemente iteriert werden
oResult.decPrice = oArticles.Select(oArticle => oArticle.decPrice.Value).Sum();
// Status des Ergebnisobjekts soll unchanged sein
oResult.State = FrameworkComponentState.Unchanged;
return oResult;
};
// GroupBy-Funktionalität als spezielles orderBy übergeben
return cdArticleCache.Get(this.Global).FillCollection(This, l => l.GroupBy(fnKeySelector, fnResultSelector));
}