# Monitoring von Datenbank-Tabellen  

<span class="custom-button petrol-button">L</span> <span class="custom-button red-button">Gen. 24 Core und Enterprise</span>

Bei dem Monitoring von Datenbanken über die COM-Aktiv-Schnittstelle werden Daten an mehreren Orten verfügbar gemacht. Über die entsprechende Einrichtung im Datenbankmanager legen Sie fest, ob Daten überwacht, überführt oder abgeglichen werden sollen.

!!! warning "Beachten Sie:"

    Folgende Voraussetzungen sind für die Nutzung der Funktion nötig:
    
    * Freie Datenbanktabelle
    * Com-Aktiv-Schnittstelle  
    
        In microtech büro+ stehen Ihnen in der Ausprägung L zwei Freie Datenbank-Tabellen zur Verfügung. Mit der Ausprägung XL können Sie bis zu zehn Freie Datenbank-Tabellen nutzen.

## Anwendungsfall und Nutzen

* Dokumentation von Datenänderungen.  
    Beispiel: Welcher Benutzer hat welche Änderung vorgenommen?
* Nur geänderte Daten bereitstellen.  
    Beispiel: Abgleich von geänderten Daten zu einen Online-Shop.

## Monitoring

### Serverseitiges Monitoring auf echte Datenbankfelder

#### Kennzeichen: Auf Feldebene (für COM-Aktiv-Schnittstelle)

Das serverseitige Monitoring kann im DB Manager für eine freie Datenbank-Tabelle im Register "Einstellungen" aktiviert werden. Hierzu ist eine freie Datenbank-Tabelle zum Ändern zu öffnen und darin das Kennzeichen „Auf Feldebene (für COM-Aktiv-Schnittstelle)“ zu aktivieren.

#### Kennzeichen: Info bereitstellen

In der Entwicklungsphase können Sie zusätzlich das Kennzeichen „Info bereitstellen“ aktivieren. Im Client der Software können Sie die Änderungen daraufhin in das InfoFeld schreiben lassen (Funktion: Feldänderungen im Info-Feld dokumentieren).

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_1.png)

### Feldänderungen protokollieren

Wechseln Sie im DB Manager der Freien Datenbank-Tabelle zum Register „Änderungsinfo-Definition“ und erstellen über NEU eine neue Änderungsinfo-Definition. Wählen Sie darin zuerst die Tabelle aus, deren Feldänderungen Sie protokollieren möchten und vergeben Sie anschließend eine Bezeichnung. In unserem Beispiel hat diese zunächst als Standard-Namen "Neue-Änderungsinfo-Definition". Wir empfehlen an dieser Stelle eine möglichst kurze, aber eindeutige Bezeichnung, da diese später zum Identifizieren innerhalb des Code für die COM-Aktiv Schnittstelle verwendet werden muss.

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_2.png)

In diesem Fenster haben Sie noch folgende Konfigurationsmöglichkeiten:

#### Gruppe Bedingung

Hier können Sie eine Bedingung für das Schreiben eines Änderungs-Datensatzes definieren. Dabei gilt: Wenn durch eine Änderung eine Bedingung WAHR wird, wird der Änderungsdatensatz mit Funktion „Neuanlage“ ausgezeichnet. Wenn bei einer Änderung die Bedingung WAHR bleibt, wird der Änderungs-Datensatz mit dem Funktionswert „Ändern“ ausgezeichnet. Wenn durch eine Änderung eine Bedingung FALSCH wird, wird der Änderungs-Datensatz mit dem Funktionswert „Löschen“ ausgezeichnet.

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_3.png)

#### Gruppe Feldauswahl für Prüfung auf Wertänderung

Sofern Sie hier eine Auswahl treffen, werden nur die markierten Felder auf eine Wertänderung geprüft. Immer wenn mindestens eines der gewählten Felder geändert wurde, wird ein Änderungsdatensatz geschrieben.

Wählen Sie keines der Felder aus, so werden alle berücksichtigt.

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_4.png)

#### Gruppe Feldwerte

* Alle Felder mit fester Länge bereitstellen (zeitintensiv)  
    Stellt alle Felder mit fester Länge bereit. Wenn das Kennzeichen nicht aktiviert wird, kann eine Einzelauswahl getroffen werden.
* Alle Felder mit variabler Länge bereitstellen (zeitintensiv)  
    Stellt alle Felder mit variabler Länge bereit. Dies kann nur aktiviert werden, wenn das Kennzeichen „Alle Felder mit fester Länge bereitstellen“ aktiviert ist.
* Alte Feldwerte bereitstellen (zeitintensiv)  
    Stellt bei Änderungen auch die alten Feldwerte bereit. Der Umfang der Felder ist analog zur Bereitstellung der Felder.

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_5.png)

#### Gruppe Kennzeichen

* Feldwerte komprimiert speichern (zeitintensiv)  
    Die serverseitige Routine wird die Daten komprimiert speichern. Das Komprimieren kostet jedoch entsprechend mehr Zeit.

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_6.png)

!!! warning "Beachten Sie:"

    Jedes Feld beansprucht ein "Mehr" an Verarbeitungszeit. Wählen Sie daher nur benötigte Felder aus.
    
    Ein Mehrfach-Monitoring ist möglich (zum Beispiel zehn Änderungsinfo-Definitionen für die Artikel-Tabelle), beeinflusst je nach eingesetzter Hardware aber die Verarbeitungszeit.
    
    Sollte die Performance nicht optimal sein, dann schränken Sie das Monitoring auf weniger Felder ein oder rüsten Sie alternativ die Hardware Ihren Ansprüchen entsprechend auf.

### Änderungsinformationen im Programm

#### Anzeige in der Übersicht

Öffnet man eine freie Datenbank-Tabelle mit Änderungsinfo, können die Änderungsinfo-Datensätze farbig dargestellt werden.

* Grau - Farbe für nicht gültige Daten: Datensatz wurde noch nicht bearbeitet (StatusCode > 0)
* Rot - Farbe für gesperrte bzw. überfällige Daten: Bei der Bearbeitung des Datensatzes kam es zu einem Fehler (StatusCode <0)

### Funktionen in der Übersicht

Befindet man sich im Register der freien Datenbank-Tabelle gibt es im Menüband unter WEITERE folgende neue Funktionen:

![Alt-Text](https://assets.hilfe.microtech.de/bilder/05_wawi/anhang/freie%20datenbank-tabellen/glfdtmonitoring_7.png)

#### Feldänderungen im Info-Feld dokumentieren

Sofern für die Tabelle ein Info-Feld in der Datenbankstruktur angelegt wurde, kann man mit dieser Routine die Änderungen im Info-Feld dokumentieren. Dazu prüft die Routine den aktuell selektierten Datensatz und übernimmt daraus den Tabellennamen und die Bezeichnung. Danach arbeitet die Routine analog zur Funktion ```IAutoModifcationMonitor.WriteAenderungsInfo```. Die Routine arbeitet endlos, bis der Anwender über Abbrechen die Funktion beendet.

#### Bereich für Feldänderungen initialisieren

Über diesen Assistenten können Sie für eine Tabelle / einen Tabellenbereich die Änderungsinfo initialisieren. Die Änderungsinfo wird aufgrund des jeweilig aktuellen Datensatz gebildet. Im Assistenten kann auch die Funktion vorgegeben werden.

!!! warning "Beachten Sie:"

    Mit dem Ausführen des Assistenten werden für die jeweilige Tabelle alle aktiven Änderunginfo-Definitionen geprüft.

## Aufbereiten der Änderungsinformationen über die COM-Aktiv-Schnittstelle

### ```IAutoModificationMonitor```

Auf der Applikation-Ebene kann über ```BpNT.Application.GetSpecialObject(soModificationMonitor)``` ein ```IAutoModificationMonitor``` aktiviert werden.

Sollte der Wert nil/null sein, wird in der vorliegenden Programmversion die Funktion nicht unterstützt.

Das Objekt bietet folgende Methoden an:

### ```function Init(const aTableName: WideString): WordBool```

Initialisiert die freie Datenbank-Tabelle zum Holen der Daten. ```aTableName``` ist der Name der freien Datenbank-Tabelle (alternativ kann hier auch der Tabellen-Kurzname angegeben werden, z.B.: "FT001" für die "FreieTabelle001"). Wenn der Rückgabewert ```TRUE``` ist, wurde die Tabelle erfolgreich initialisiert. Wenn der Rückgabewert ```FALSE``` ist, wurde die Änderungsinformation auf Feldebene nicht aktiviert.

### ```procedure Setup(const aTableName: WideString; const aBezeichnung: WideString)```

Diese Methode definiert den Bearbeitungs-Bereich, da man in eine freie Datenbank-Tabelle mehrere Tabellen und Bereiche definieren kann. aTableName ist der Name der Tabelle, von der man die Änderungen Monitoren will (alternativ kann hier auch der Tabellen-Kurzname angegeben werden, z.B.: "Art" für die "Artikel"). aBezeichnung ist die Bezeichnung, die man im DB Manager - freie Datenbank-Tabelle wählen - Struktur ändern - "Änderungsinfo-Definition" - Bezeichnung hinterlegt hat.

### ```function AcquireFieldList(const aFields: WideString): Integer```

Damit legt man eine Feldliste an, \<Name der Felder> enthält die Feldnamen der gewünschten Felder der unter ```IAutoModificationMonitor.Setup``` angegebenen Tabelle (mit Komma oder Semikolon getrennt). Die Felder müssen in der Tabelle vorhanden sein. Bei den Feldnamen kann ein Werte-Typ angegeben werden, um die Werte spezifisch auszulesen. Der Feldtyp wird hinter den Feldnamen geschrieben und mit einem Doppelpunkt von ihm getrennt. Der Wertetyp kann einer dieser Typen sein (Schreibweise beachten! Groß/Kleinschreibung ist nicht relevant) 'Integer', 'Int64', 'Float', 'String', 'Text', 'DisplayText', 'DateTime', 'Boolean', 'DataString', 'DataStream', 'DSReference', 'DSFileName', 'DSFileExtension', 'DataSet'

### Rückgabewert: ein Handle auf diese FeldListe \[Integer\]

Falls Felder abgefragt werden, die nicht vorhanden sind, wird bei Lese-Anfragen im zurückgegebenen Array an dieser Position kein Wert übergeben, bei Schreibe-Anfragen das Feld nicht beachtet. Falls ausschließlich Felder abgefragt werden, die nicht vorhanden sind, wird -1 zurückgeliefert.

### Syntax: "Feldname1;Feldname2:Integer;Feldname3;Feldname4:string;Feldname5"

Die Methode verhält sich analog zu ```AutoDataSet.AcquireFieldList```.

### function ReadFieldListValues(aHandle, aOldValueMode: Integer): OleVariant

Damit erhält man die Werte der angefragten Felder als Variant-Array. Die Werte im Array stehen in der Reihenfolge, die bei ```AcquireFieldList``` angegeben wurde. Besonderheit: Sofern man bei ```AcquireFieldList``` nur einen Feldnamen angegeben hat, erhält man direkt den Feldwert als Variante (d.h. kein Variant-Array). Neben der normalen Belegung einer Variante können die Varianten zwei weitere Zustände enthalten: 1. Unassigned - Zugriff ist nicht möglich (z.B.: Feldname existiert nicht, Feld ist zugriffsgeschützt, ...) 2. Null - Feldwert wurde nicht belegt, hierüber kann beispielsweise bei Zahlen die Unterscheidung zwischen Benutzer hat eine "0" hinterlegt oder Benutzer hat im Feld nichts eingegeben getroffen werden.

Für .NET gilt:

1. Null = Unassigned

2. ```{} = Null aOldValueMode``` spezifiziert, welche Feldwerte zurückgegeben werden sollen:

0 = Aktuelle Feldwerte. D.h. bei NEU sind es die Feldwerte nach der Neuanlage des jeweiligen Datensatz. Bei ÄNDERN sind es die Feldwerte nach der Änderung des jeweiligen Datensatz. Bei LÖSCHEN sind es die Feldwerte vor dem LÖSCHEN.

1 = Sofern man in der Änderungsinfo-Definition das Kennzeichen "Alte Feldwerte bereitstellen" aktiviert hat, kann man hier auf die alten Feldwerte zugreifen. Die alten Feldwerte stehen bei allen Änderungen zur Verfügung. Anmerkung: Sofern man eine "Bedingung" in der Änderungsinfo-Definition hinterlegt hat. Hier wird bei einer Änderung der Funktionscode für NEU zurückgegeben, daher stehen bei solchen Änderungen auch die alten Feldwerte zur Verfügung.

2 = Über diese Angabe erhält man im Rückgabe-Variant ein Array mit zwei Variant-Listen zurück. Man erhält damit die aktuellen Feldwerte und die alten Feldwerte (dies erspart einen weiteren Aufruf der Funktion). Im ersten Eintrag (Rückgabe-Variant\[0\]) erhält man den Rückgabewert wie unter ```aOldValueMode = 0``` und im zweiten Eintrag (Rückgabe-Variant\[1\]) erhält man den Rückgabewert wie unter ```aOldValueMode = 1```.

Siehe auch ```AutoDataSet.ReadFieldListValues```.

### ```function ReadFieldListDiffValues(aHandle, aOldValueMode: Integer): OleVariant```

Diese Methode erlaubt das Lesen der nur geänderten Feldwerte. aHandle bezeichnet die FeldListe, die zuvor mit AcquireFieldList definiert wurde. Sofern man einen Feldwert immer benötigt, kann man beim spezifizieren über AcquireFieldList vor dem FeldNamen ein „!“ (Ausrufezeichen) einfügen. Dies ist dafür gedacht, um den beim replizieren von Daten, den zu bearbeitenden Datensatz zu spezifizieren (z.B.: Lagerbestandsabgleich, Artikelnummer / Lagernummer ändern sich nicht, über "```!ArtNr,!LagNr,Mge```" erhält man immer Artikelnummer und Lagernummer immer). Auch kann über ```AcquireFieldList``` für die Listenverarbeitung einen freidefinierbaren FeldIndex (Typ: Integer) definieren der anstelle des Feldnamens zurückgegeben wird, indem man ":IX\[optionale Zahl\]" direkt an den Feldnamen anhängt.

Zum Beispiel: "```!ArtNr:IX,!LagNr:IX,Mge:IX```" ergibt in der Rückgabe 0,1,2 anstelle der Feldnamen (der Wert entspricht dem 0 basierten Index der Feldnamensliste). "```!ArtNr:IX10,!LagNr:IX11,Mge:IX12```" ergibt in der Rückgabe 10,11,12 anstelle der Feldnamen.

aOldValueMode spezifiziert welche Feldwerte zurückgegeben werden sollen:

0 = Aktuelle geänderte Feldwerte. Im Rückgabe-Variant steht ein Array mit zwei Variant-Listen: Der erste Eintrag (Rückgabe-Variant\[0\]) spezifiziert die Feldnamen des zweiten Eintrags. Der zweite Eintrag enthält die Liste der aktuellen Feldwerte.

1 = Alte Feldwerte. Im Rückgabe-Variant steht ein Array mit zwei Variant-Listen: Der erste Eintrag (Rückgabe-Variant\[0\]) spezifiziert die Feldnamen des zweiten Eintrags, der zweite Eintrag enthält die Liste der alten Feldwerte.

2 = Aktuelle und alte geänderte Feldwerte. Im Rückgabe-Variant steht ein Array mit drei Variant-Listen: Der erste Eintrag (Rückgabe-Variant\[0\]) spezifiziert die Feldnamen des zweiten und dritten Eintrags, der zweite Eintrag enthält die Liste der aktuellen Feldwerte und der dritte Eintrag enthält die Liste der alten Feldwerte.

3 = Alte Feldwerte für Felder die mit „!“ ausgezeichnet wurden, ansonsten neue Feldwerte. Im Rückgabe-Variant steht ein Array mit zwei Variant-Listen: Der erste Eintrag (Rückgabe-Variant\[0\]) spezifiziert die Feldnamen des zweiten Eintrags. Der zweite Eintrag enthält die Liste der Feldwerte, dabei gilt: Felder mit „!“ können zweimal vorkommen, das erste Vorkommen enthält den alten Feldwert, das zweite Vorkommen den neuen Feldwert. Dies kann vorkommen, wenn man z.B. den Lagerbestand repliziert und der Anwender einen Artikel auf eine neue Nummer verschiebt. In diesem Fall bekommt man bei einer Feldliste für "```!ArtNr,!LagNr,Mge```" als Rückgabe-Variant-Wert "```ArtNr,LagNr,ArtNr```" und beispielsweise "1,1,100" für Artikel 1 wurde auf die Artikelnummer 100 mit Lagernummer 1 verschoben.

### ```procedure ReleaseFieldList(aHandle: Integer)```

Damit gibt man die mit ```AcquireFieldList``` angelegte Feldliste wieder frei, wenn man sie nicht mehr benötigt. Alle Feldlisten zu diesem Dataset werden bei Freigabe des Datasets automatisch freigegeben.

Die Methode verhält sich analog zu ```AutoDataSet.ReleaseFieldList```.

### ```function FindFirst(out aFunktionCode: Integer): WordBool```

Mit dieser Funktion öffnet man das Monitoring. Wenn der Rückgabewert ```FALSE``` ist, gibt es aktuell keine Änderungen in dem angegebenen Bereich. Bei ```TRUE``` bekommt man als ```aFunktionCode``` die Werte 0 = nicht spezifiziert, 1 = neu angelegt, 2 = geändert, 5 = gelöscht für den ersten Änderungsdatensatz zurück. Ab hier ist der Zugriff über ```ReadFieldListValues```, ```ReadFieldListDiffValues``` und ```WriteAenderungsInfo``` erlaubt.

### ```function FindNext(aStatusCode: Integer; out aFunktionCode: Integer): WordBool```

Mit dieser Funktion wird die Bearbeitung des aktuellen Änderungsdatensatz abgeschlossen und auf den nächsten Datensatz gefahren.

Für den Abschluss des aktuellen Änderungsdatensatz muss der ```aStatusCode``` angegeben werden. Dabei hat ```aStatusCode``` folgende Bedeutung:

* Negative Zahl = „Fehler“

* 0 = Datensatz wurde verarbeitet

* Positive Zahl = Änderungsdatensatz bleibt bestehen

Der Rückgabewert ist ```FALSE```, wenn keine weitere Änderung verzeichnet wurde. Bei ```TRUE``` bekommt man den aFunktionCode für den nächsten Änderungsdatensatz zurück.

siehe auch ```FindFirst.aFunktionCode```.

### ```procedure FindClose```

Mit dieser Funktion schließt man das Monitoring. Diese Methode muss in einem ```finally``` Bereich stehen, da im Exception- bzw. Fehlerfall ansonsten ein Lock-Befehl stehen bleiben könnte.

### ```function WriteAenderungsInfo(out aStatusCode: Integer): WordBool;```

Schreibt in das Info-Feld des Änderungsdatensatz die Feldänderungen im Klartext. Wenn der Rückgabewert ```FALSE``` ist, ist für die Tabelle kein Info-Feld verfügbar. aStatusCode ist fest auf 1 implementiert, sodass der Datensatz als bearbeitet stehen bleibt.

### Beispiel-Code

Beispielaufbau eines Monitors in Delphi-Quellcode:

```delphi
var hIMonitor:IAutoModificationMonitor;

hFieldListHandle:Integer;

iStatusCode:Integer;

iFunktionCode:Integer;

hValues:Integer;

hIMonitor := nil;

hApp := ComApplication as BpNT.Application;

try

hIMonitor := hApp.GetSpecialObject(soModificationMonitor) as IAutoModificationMonitor;

if not hIMonitor.Init(\[Name bzw. Kurzname der Freie Datenbank-Tabelle\]) then exit;

hIMonitor.Setup(

\[Tabellenname oder Kurzname analog zu freie Datenbank-Tabelle - DB Manager - Struktur ändern - "Änderungsinfo-Definition" - Tabellendefinition\],

\[Bezeichnung analog zu freie Datenbank-Tabelle - DB Manager - Struktur ändern -

"Änderungsinfo-Definition" - Bezeichnung\]

);

hFieldListHandle := hIMonitor.AcquireFieldList(\[FeldNamen kommasepariert\]);

try

repeat

if hIMonitor.FindFirst(iFunktionCode) then

begin

try

repeat

iStatusCode := 0; // negative Zahlen "Fehler", Datensatz bleibt bestehen; 0 =

Datensatz wurde verarbeitet, "löschen", positive Zahlen Daten wurde verarbeitet, Datensatz bleibt bestehen

// iFunktionCode: 0 = nicht spezifiziert, 1 = neu angelegt, 2 = geändert, 5 = gelöscht

hValues := hIMonitor.ReadFieldListValues(hFieldListHandle, 2);

// hier hValues verarbeiten

until not hIMonitor.FindNext(iStatusCode, iFunktionCode);

finally

hIMonitor.FindClose;

end;

end;

Delay(500); // Wichtig 500 ms Warten oder etwas anderes machen, bis wieder / weitere Änderungen vorligen

until \[Abbruch Bedingung\];

finally

hIMonitor.ReleaseFieldList(hFieldListHandle);

end;

finally

hIMonitor := nil;

hApp := nil;

end;

Beispielaufbau eines Monitors in C#-Quellcode:

BpNT.Application hApp = new BpNT.Application();

AutoModificationMonitor hIMonitor = (AutoModificationMonitor)hApp.GetSpecialObject(SpecialObjects.soModificationMonitor);

int hFieldListHandle;

int iStatusCode;

int iFunktionCode;

object\[\] hValues = null;

try

{

hIMonitor.Init(\[Name bzw.Kurzname der Freie Datenbank - Tabelle\]);

hIMonitor.Setup(\[Tabellenname oder Kurzname analog zu freie Datenbank - Tabelle - DB Manager - Struktur ändern - "Änderungsinfo-Definition" - Tabellendefinition\], \[Bezeichnung analog zu freie Datenbank-Tabelle - DB Manager - Struktur ändern - "Änderungsinfo-Definition" - Bezeichnung\]);

hFieldListHandle = hIMonitor.AcquireFieldList(\[FeldNamen kommasepariert\]);

do

{

if (hIMonitor.FindFirst(out iFunktionCode))

{

do

{

iStatusCode = 0; // negative Zahlen "Fehler", Datensatz bleibt bestehen; 0 = Datensatz wurde verarbeitet, "löschen", positive Zahlen Daten wurde verarbeitet, Datensatz bleibt bestehen

// iFunktionCode: 0 = nicht spezifiziert, 1 = neu angelegt, 2 = geändert, 5 = gelöscht

if (iFunktionCode == 1 || iFunktionCode == 2)

{

hValues = hIMonitor.ReadFieldListValues(hFieldListHandle, 0);

// hier hValues verarbeiten – Neuanlage und Änderung

}

if (iFunktionCode == 5)

{

hValues = hIMonitor.ReadFieldListValues(hFieldListHandle, 0);

// hier hValues verarbeiten - Löschen

}

} while (hIMonitor.FindNext(iStatusCode, out iFunktionCode) && \[Abbruchbedingung\]);

}

Thread.Sleep(500); // Wichtig, 500 ms warten oder etwas anderes machen, bis wiederweitere Änderungen vorliegen

} while (\[Abbruchbedingung\]);

}

catch (Exception ex)

{

//Fehlerprotokollierung

}

finally

{

hIMonitor.FindClose();

hlMonitor = null;

hApp = null;

}
```