Erweiterte Programmierung


"Speicherbare Abfragen" sind Abfragen, die vom Endanwender an der Benutzeroberfläche individuell geändert und evtl. auch persistiert werden können für eine spätere Wiederverwendung. Der Endanwender editiert die Abfrage mit dem Businessobject "gip.core.autocomponent.VBBSOQueryDialog". Es wird geöffnet entweder über das Search-Icon in der Ribbon-Bar oder per F3-Taste auf einer Combobox.

Der Query-Dialog besteht aus vier Registerkarten, wobei folgende drei relevant sind:

Auf der linken Bildschirmhälfte werden die Eigenschaften angezeigt, die per ACEntityProperty-Attribut in der partiellen Entity-Klasse deklariert worden sind. Der Anwender kann per Drag-And-Drop die Eigenschaften in die Filterliste ziehen, um die Abfrage zu verändern. In den Spalten können Verknüpfungsoperatoren und Vergleichsoperatoren gesetzt werden, um die Abfragebedingung zu formulieren. Mit jeder weiteren Zeile wird eine weitere Abfragebedingung formuliert. Mit der Taste "Neuer Filter" können zusätzliche Zeilen hinzugefügt werden um z.B. Klammern zu setzen.

In der Sortier-Registerkarte können dann die Sortierkriterien nach demselben Schema formuliert werden:

 

Aus der Filterliste wird zuletzt eine "WHERE"-Klausel und aus der Sortierliste eine "ORDER BY"Klausel in der EntitySQL-Sprache gebildet, die in der letzten Registerkarte angeschaut und angepasst werden kann:

 

Mit der Checkbox "Konfiguration speichern" kann die Abfrage persistiert werden, damit sie das nächste Mal beim erneuten Öffnen des Businessobjektes wiederhergestellt wird. Mit den vier Tasten neben der Checkbox wird definiert, ob die Persistierung für diesen Computer, Benutzer oder Generell für alle gilt.

Wie speicherbare Abfragen programmgesteuert deklariert, manipuliert und verwendet werden ist in den folgenden Abschnitten beschrieben.


gip.core.datamodel.ACQueryDefinition

Die Kasse gip.core.datamodel.ACQueryDefinition ist eine serialisierbare Klasse. Aus ihren Eigenschaftswerten generiert sie einen Entity-SQL-Ausdruck der mittels "System.Data.Objects.ObjectQuery<T>" ausgeführt  und mit zusätzlichen LINQ-Befehlen erweitert werden kann. Die ACQueryDefinition-Klasse besitzt folgende Eigenschaften:

ParameterBeschreibung
string SearchWordGlobales Suchwort das in der Ribbon-Bar und oben im Query-Dialog angezeigt wird.

 int TakeCount

Beschränkt die Zeilen, die in einem Abfrageergebnis zurückgegeben werden. Dieser Wert wird an die Take-Erweiterungs-Methode übergeben und in die TOP-Klausel in SQL konvertiert.

List<ACColumnItem> ACColumns 

Verfügbare Spalten, die im Query-Dialog auf der linken Seite angezeigt werden.

BindingList<ACFilterItem> ACFilterColumns

Filterliste, die in der Filter-Registerkarte im Query-Dialog angezeigt wird.

BindingList<ACSortItem> ACSortColumns

Sortierliste, die in der Sortier-Registerkarte im Query-Dialog angezeigt wird.
string EntitySQL_FromItemsEntity-SQL-Abfrage, die aus den Einträgen in der Filter und Sortierliste gebildet wird.
string EntitySQL_FromEditKopie von EntitySQL_FromItems, die verändert werden kann.
string EntitySQLEndgültige Entity-SQL-Abfrage zur Datenbankausführung. Falls EntitySQL_FromItems verändert wurde, dann wird diese verwendet sonst EntitySQL_FromItems.

gip.core.datamodel.ACFilterItem:

 ACFilterItem entspricht einer Filterzeile in der ACFilterColumns-Liste.

ParameterBeschreibung
string PropertyName

Relativer Pfad (mit Punkt getrennt) zur Eigenschaft (bzw. Tabellenfeld) nach der gefiltert werden soll.

string SearchWord

Zeichenkette die zur Filterung verwendet werden soll. Verwenden Sie die folgenden Methoden um den Filterwert zu setzen oder zu lesen:

T GetSearchValue<T>()
void SetSearchValue<T>(T value)

T gibt den Datentypen der Eigenschaft an auf die "PropertyName" verweist.

Global.FilterTypes FilterType

Beschreibt, ob das FilterItem

  • eine Suchbedingung (FilterTypes.filter) ist
  • oder eine Klammer auf (FilterTypes.parenthesisOpen)
  • oder eine Kammer zu (FilterTypes.parenthesisClose)

ausdrückt.

Global.LogicalOperators LogicalOperator

Vergleichsoperatoren und logische operatoren:

public enum LogicalOperators : short
{
// Undefined
none = 0,
// Equal: =
equal = 1,
// Not equal: !=
notEqual = 2,
// Less than: <
lessThan = 3,
// The greater than: >
greaterThan = 4,
// Less than or equal: <=
lessThanOrEqual = 5,
// Greater than or equal: >=
greaterThanOrEqual = 6,
// String.StartsWith(x); SQL: LIKE 'x%'
startsWith = 7,
// String.EndsWith(x); SQL: LIKE '%x'
endsWith = 8,
// string.Contains(x); SQL: LIKE '%x%'
contains = 9,
// The is null operator
isNull = 10,
// The is not null operator
isNotNull = 11,
// !string.Contains(x); SQL: NOT LIKE '%x%'
notContains = 12,
}

Global.Operators Operator

Verknüpfungsoperatoren:

public enum Operators : short
{
none = 0,
or = 1,
and = 2,
}

 

bool IsConfigurationKennzeichen, ob die Filterzeile aus dem Programmcode erstellt wurde. Falls "False", dann wurde die Fitlterzeile vom Anwender per GUI angelegt.
bool UsedInGlobalSearchKennzeichen, ob das globale Suchwort ("gip.core.datamodel.ACQueryDefinition.SearchWord") in diese Filterzeile als Suchwort verwendet werden soll. 

 

gip.core.datamodel.ACSortItem:

 ACSortItem entspricht einer Sortierzeile in der ACSortColumns-Liste.

ParameterBeschreibung
string PropertyName

Relativer Pfad (mit Punkt getrennt) zur Eigenschaft (bzw. Tabellenfeld) nach der sortiert werden soll.

Global.SortDirections SortDirection

Beschreibt, die Sortierrichtung:

  • Aufsteigend (FilterTypes.ascending)
  • oder Absteigend (FilterTypes.descending)
bool IsConfigurationKennzeichen, ob die Sortierzeile aus dem Programmcode erstellt wurde. Falls "False", dann wurde die Fitlterzeile vom Anwender per GUI angelegt.

 


Bevor Sie eine speicherbare Abfrage (ACQueryDefinition) verwenden bzw. instanziieren können, muss zuerst mit der ACQueryInfoPrimary- oder ACQueryInfo-Klasse eine Registrierung für das iPlus-Framework erfolgen. Verwenden Sie ACQueryInfoPrimary oberhalb der Klassendeklaration einer Entity-Framework-Klasse, weil damit die primäre Abfrage für eine Datenbanktabelle definiert wird. Falls sie weitere Abfragen für die gleiche Datenbanktabelle benötigen verwenden Sie die ACQueryInfo-Klasse, die in der Regel oberhalb eines Geschäftsobjektes (Ableitungen von ACBSO) deklariert wird.

 

Eine ACQueryDefinition mit ACQueryInfo registrieren

 

Der Konstruktor besitzt acht Parameter:

ParameterBeschreibung
string acPackageNamePaketname

 string acName

Eindeutiger Name der Abfrage, der mit dem Prefix "QRY" beginnen muss. Verwenden Sie dafür die Konstante "gip.core.datamodel.Const.QueryPrefix" zur Verkettung des gesamten Strings. z.B. 

Const.QueryPrefix + "Material"

string  acCaptionTranslation

Mehrsprachige Beschreibung (Übersetzungstupel).

Type queryType

Typ der Entitäts-Klasse bzw. Tabellenname für die diese Abfrage definiert wird. (Siehe violetten Rahmen im Bild oben.)

string childACUrl

Falls diese ACQueryInfo zur Abfrage für eine Kind-Beziehung dient, dann muss hier der Name der Navigations-Eigenschaft (EntityCollection<T>, EdmRelationshipNavigationPropertyAttribute) angegeben werden die in der Eltern-Entitäts-Klasse deklariert ist. Kind-ACQueryInfos werden in ACQueryChilds-Liste eingetragen. Siehe Konstruktor-Eigenschaft "acQueryChilds". Bei Wurzel-ACQueryInfo's geben Sie einfach den Entitäts-Klassennamen an. (Siehe orangenen Rahmen im Bild oben).
string vbFilterKommaseparierte Liste von Eigenschaftsnamen, die bei der Konstruktion einer ACQueryDefinition als ACFilterItem in der ACFilterColumns-Liste eingetragen werden. (Siehe roten Rahmen im Bild oben).
string vbSortKommaseparierte Liste von Eigenschaftsnamen, die bei der Konstruktion einer ACQueryDefinition als ACSortItem in der ACSortColumns-Liste eingetragen werden. (Siehe grünen Rahmen im Bild oben).
object[] acQueryChildsAuflistung, die wiederum ACQueryInfo's enthält, um die Kind-Entitäts-Beziehungen zu beschreiben z.B. für Kopf- und Positionsdaten. (Siehe schwarzen Rahmen im Bild oben).

Während des Registriervorgangs werden für die ACQueryInfo-Deklaration neue iPlus-Typdefinitionen (Tabelle ACClass) in der Datenbank angelegt wie auch bei ACClassInfo-Deklarationen für ACComponents. Sie erscheinen in der Entwicklungsumgebung im iPlus-Baum unter dem Abschnitt "Queries":

 


Um eine ACQueryDefintion zu instanziieren, verwenden Sie die Methode "CreateQuery" der Queries-Instanz:

ACQueryDefinition acQueryDef = Root.Queries.CreateQuery(null, Const.QueryPrefix + "Material", ComponentClass.ACIdentifier);

Im ersten Parameter wird das Eltern-Objekt übergeben. Übergeben Sie hier null oder den this-Pointer der ACComponent in der Sie diesen Aufruf machen. (Bei hierarchischen Abfragen, wird bei der Kind-ACQueryDefinition die Eltern-QueryDefinition übergeben).

Der zweite Parameter ist der  eindeutige Name der Query-Klasse, der mit per acName-Parameter in der ACQueryInfo-Attributklasse registriert wurde.

Der letzte Parameter gibt an, unter welchem Schlüsselnamen diese ACQueryDefinition gespeichert und bei erneuter Verwendung gelesen werden soll (Siehe Thema "Konfiguration speichern" im ersten Abschnitt). In der Regel verwenden Sie den Klassennamen der ACComponent in der Sie diese speicherbare Abfrage nutzen (ComponentClass.ACIdentifier).

 

Primäre speicherbare Abfragen

ACComponent bzw. Ableitungen der ACBSO (Geschäftsobjekte) werden per ACClassInfo-Attribut dem IPlus-Framework bekannt gegeben. Der siebte Parameter "string qryConfig" im Konstruktor gibt den Namen der primären ACQueryDefinition an um zu beschreiben, mit welcher primären Tabelle das Geschäftsobjekt arbeitet. Bei einer Geschäftsobjektklasse, die zur Verwaltung der Materialstammdaten dient, wird der Parameter qryConfig mit dem Wert "QRYMaterial" (bzw. Const.QueryPrefix + "Material") gesetzt.

Für die Instanziierung einer primären ACQueryDefinition, stellt Queries eine weitere, alternative Methode "CreateQuerybyClass()" zur Verfügung:

ACQueryDefinition acQueryDef = Root.Queries.CreateQuerybyClass(null, ComponentClass.PrimaryNavigationquery(), ComponentClass.ACIdentifier);

Dabei wird direkt die ACClass der primären speicherbaren Abfrage übergeben, indem die Hilfsmethode PrimaryNavigationquery() aufgerufen wird.

Ihnen steht es frei welche CreateQuery-Methode Sie verwenden. Diese Metabeschreibung, welches die primäre ACQueryDefintion für eine ACCompoent-Klasse ist, ist vorallem für die WPF-Steuerelemente

  • VBComboBox,
  • VBListBox,
  • VBTreView
  • und VBDataGrid

aus der "gip.core.layoutengine"-Assembly notwendig. VBComboBox wird vorwiegend, zum setzten einer NavigationsEigenschaft (Fremdschlüsselfeld in einer Tabelle) verwendet. Damit die Datensatzauswahl in der Combobox reduziert werden kann und der Benutzer per F3-Taste suchen kann wird ebenfalls eine ACQueryDefinition-Instanz automatisch generiert. Die ComboBox sucht dabei standardmäßig die primäre ACQueryDefinition, wenn nicht explizit etwas anderes vom Programmierer definiert worden ist.


ACAccess<T>

Mit der Instanziierung einer ACQueryDefinition ist zwar nun die Beschreibung der Datenbankabfrage ermöglicht worden jedoch müssen die Daten noch auf der Datenbank abgefragt werden. Für das Ausführen der speicherbaren Abfrage ist die Klasse "gip.core.datamodel.ACAccess<T>" zuständig.

Eine ACAccess-Instanz erhalten sie durch Aufruf der Methode "ACQueryDefinition.NewAccess()":

ACAccess<Material> acAccess = acQueryDef.NewAccess<Material>(Material.ClassName, this);

Danach rufen Sie eine der überladenen Methoden "NavSearch(...)" auf und übergeben einen Datenbankkontext, auf dem die Abfrage ausgeführt werden soll:

bool succ = acAccess.NavSearch(dbContext);
bool succ = acAccess.NavSearch(dbContext, MergeOption.OverwriteChanges);

Das Abfrageresultat wird in die Eigenschaft public IList<T> NavList geschrieben:

 foreach (Material mat in AccessPrimary.NavList)
{
// do something
}

 

ACAccessNav<T>

Die abgeleitete Klasse ACAccessNav<T> erweitert die ACAccess<T> um Navigationsmethoden:

  • public void NavigateFirst();
  • public void NavigateLast();
  • public void NavigateNext();
  • public void NavigatePrev();
  • T Current { get; }

ACAccessNav<T> wird für navigierbare Geschäftsobjekte, die von der Basisklasse ACBSONav abgeleitet sind. Sie erhalten eine Instanz durch Aufruf der Methode NewAccessNav:

ACAccessNav<Material> _AccessPrimary = acQueryDef.NewAccessNav<Material>(Material.ClassName, this);

Überschreiben Sie in Ihrer Ableitung die Eigenschaft AccessNav, um Ihre Instanz von ACAccessNav<T> zurückzugeben:

public override IAccessNav AccessNav { get { return AccessPrimary; } }

Wird Ihr Geschäftsobjekt geöffnet und an der Oberfläche angezeigt, wird in der VBRibbonBar der Abschnitt zur Navigation mit den Navigationstasten sichtbar, da die überschriebene Eigenschaft AccessNav nun eine Instanz vom Interface IAccessNav zurückgibt, das ACAccessNav<T> implementiert. Die Navigationstasten werden automatisch per CommandBinding an die vier Navigationsmethoden gebunden.

 

Globales Suchwort

Wird die Enter-Taste in der Search-Textbox in der Ribbon-Bar gedrückt, wird ein weiteres CommandBinding ausgeführt, das an die Methode "Search()" des Geschäftsobjektes gebunden ist. Diese Methode müssen Sie explizit deklarieren und entsprechend folgendem Beispiel implementieren:

[ACMethodCommand(Material.ClassName, "en{'Search'}de{'Suchen'}", (short)MISort.Search)]
public void Search()
{
AccessPrimary.NavSearch(DatabaseApp, DatabaseApp.RecommendedMergeOption);
OnPropertyChanged("MaterialList");
}

[ACPropertyList(9999, Material.ClassName)]
public IList<Material> MaterialList
{
get
{
return AccessPrimary.NavList;
}
}

Die MaterialList enthält das Abfrageresultat und wird in der Regel an ein VBDataGrid im Explorer-Fenster gebunden. Damit das VBDataGrid das Abfrageresultat anzeigt lösen Sie das PropertyChanged-Event aus per Aufruf der Methode OnPropertyChanged(...)..


Filterbedingungen ändern

Beispiel zum Hinzufügen von Filterbedingungen:

// Open bracket:
acQueryDef.ACFilterColumns.Add(new ACFilterItem(Global.FilterTypes.parenthesisOpen, null, Global.LogicalOperators.none, Global.Operators.and, null, true));

// Filter with empty searchword (null):
acQueryDef.ACFilterColumns.Add(new ACFilterItem(Global.FilterTypes.filter, "MaterialNo", Global.LogicalOperators.contains, Global.Operators.or, null, true, true));

// Filter with default-searchword "ABC":
acQueryDef.ACFilterColumns.Add(new ACFilterItem(Global.FilterTypes.filter, "MaterialName1", Global.LogicalOperators.contains, Global.Operators.or, "ABC", true, true));

// Close bracket:
acQueryDef.ACFilterColumns.Add(new ACFilterItem(Global.FilterTypes.parenthesisClose, null, Global.LogicalOperators.none, Global.Operators.and, null, true));

Beispiel zum Hinzufügen einer Sortierbedingung:

acQueryDef.ACSortColumns.Add(new ACSortItem("MaterialNo", Global.SortDirections.ascending, true));

Speichern einer ACQueryDefintion:

acQueryDef.SaveConfig();

 

Filterbedingungen vergleichen

Falls der Anwender eine speicherbare Abfrage so geändert und gespeichert hat, dass die eigentliche Grundfunktion des Geschäftsobjektes nicht mehr gewährleistet ist, ist es evtl. notwendig die Filter-Grundeinstellung wiederherzustellen. Dazu stellt die ACQueryDefinition einige Vergleichsmethoden bereit, mit denen die aktuelle Filtereinstellung mit der Grundeinstellung verglichen werden kann:

  • bool CompareFilterColumns(IEnumerable<ACFilterItem> filterItems,...); Vergleicht die übergebene Filterliste mit der aktuellen (ACFilterColumns). Falls diese unterschiedlich sind, wird true zurückgegeben.
  • bool CompareSortColumns(IEnumerable<ACSortItem> sortItems,...); Vergleicht die übergebene Sortierliste mit der aktuellen (ACFilterColumns). Falls diese unterschiedlich sind, wird true zurückgegeben.
  • bool CompareColumns(IEnumerable<ACFilterItem> filterItems, IEnumerable<ACSortItem> sortItems,...); Vergleicht die Filterliste und die Sortierliste. Falls diese unterschiedlich sind, wird true zurückgegeben.
  • bool CheckAndReplaceFilterColumnsIfDifferent(IEnumerable<ACFilterItem> filterItems,...); Vergleicht die übergebene Filterliste mit der aktuellen (ACFilterColumns). Falls diese unterschiedlich waren, wird die aktuelle mit der übergebenen ersetzt und true zurückgegeben.
  • bool CheckAndReplaceSortColumnsIfDifferent(IEnumerable<ACSortItem> sortItems,...); Vergleicht die übergebene Sortierliste mit der aktuellen (ACFilterColumns). Falls diese unterschiedlich waren, wird die aktuelle mit der übergebenen ersetzt und true zurückgegeben.
  • bool CheckAndReplaceColumnsIfDifferent(IEnumerable<ACFilterItem> filterItems, IEnumerable<ACSortItem> sortItems,...); Vergleicht die Filterliste und die Sortierliste und ersetzt gegebenenfalls.

Wir empfehlen die Filter- und Sortiergrundeinstellung als private Eigenschaften im Geschäftsobjekt zu deklarieren und nach der Instanziierung der ACQueryDefinition den Vergleich durchzuführen: 

 


ACAccess<T> stellt das Ereignis "event NavSearchEventHandler NavSearchExecuting" zur Verfügung, um die Abfrage mit zusätzlichen Filterbedingungen per LINQ erweitern zu können. Bevor dieses Ereignis ausgelöst wird, wird zunächst aus der ACQueryDefinition eine Ausdrucksbaumstruktur erstellt und als IQueryable<T> dem Ereignis übergeben. Im Eventhandler können Sie dann wie gewohnt die Ausdrucksbaumstruktur per LINQ erweitern:

Im Event-Handler "_AccessPrimary_NavSearchExecuting" können Sie den parameter result in eine ObjectQuery<T>-Klasse casten um z.B. zusätzliche Include-Anweisungen einzufügen, um späteres Lazy-Loading zu vermeiden und die Performance Ihrer Anwendung zu verbessern.