Praxisbeispiel: Adressen, Anschriften und Ansprechpartner verwalten (GraphQL)
Dieses Beispiel zeigt, wie Sie über GraphQL Adressen mit ihren Anschriften und Ansprechpartnern lesen, anlegen, ändern und löschen. Dabei wird die verschachtelte Tabellenstruktur über Links navigiert, sodass zusammengehörige Daten in einer einzigen Operation verarbeitet werden können.
Grundlegende GraphQL-Kenntnisse werden vorausgesetzt. Sollten Ihnen Konzepte wie Tabellenzugriff, Verknüpfungen oder Filter noch nicht vertraut sein, empfehlen wir zunächst die GraphQL Doku - Abfragen (Queries). Für Mutationen siehe GraphQL Doku - Mutationen.
Inhaltsverzeichnis
- Überblick
- Adressen lesen
- Adresse mit Anschrift und Ansprechpartner anlegen
- Adresse mit Anschrift und Ansprechpartner ändern
- Adresse löschen
- Erweiterte Muster
- Tipps und Stolpersteine
1. Überblick
Die Adressverwaltung in microtech ERP verteilt Stammdaten auf drei verknüpfte Tabellen.
-
Eine Adresse (
tblAddresses) kann mehrere Anschriften (tblPostalAddresses) besitzen -
Jede Anschrift kann mehrere Ansprechpartner (
tblContactPeople) enthalten.
Tabellenstruktur
tblAddresses (1)
└─ lnkPostalAddresses → tblPostalAddresses (N)
└─ lnkContactPeople → tblContactPeople (N)
| Tabelle | Beschreibung | Schlüsselfeld |
|---|---|---|
tblAddresses | Stammdaten der Adresse (Suchbegriff, Status, Info) | fldAdrNr |
tblPostalAddresses | Anschriftdaten (Name, Straße, PLZ, Ort) | fldAdrNr + fldAnsNr |
tblContactPeople | Ansprechpartner (Name, Funktion, Kontaktdaten) | fldAdrNr + fldAnsNr + fldAspNr |
Die Navigation zwischen den Tabellen erfolgt über Links (lnk...). Dieser verschachtelte Zugriff ist der empfohlene Weg, um zusammengehörige Daten in einer Operation zu lesen oder zu schreiben.
Beachten Sie
-
Alle drei Tabellen sind auch einzeln über
tblAddresses,tblPostalAddressesundtblContactPeopleerreichbar. -
Für den typischen Anwendungsfall — Adresse mit Anschriften und Ansprechpartnern — ist der verschachtelte Zugriff über Links jedoch übersichtlicher und performanter.
2. Adressen lesen
2.1 Einzelne Adresse mit Anschriften und Ansprechpartnern
Die folgende Query liest eine Adresse per Adressnummer und navigiert über Links zu den zugehörigen Anschriften und Ansprechpartnern.
query AdresseMitAnschriftenUndAnsprechpartnern {
tblAddresses {
# Adresse per keyFilter lesen (schnellster Zugriff)
rowRead(kf1AdrNr: { text: "10001" }) {
fldAdrNr # Adressnummer
fldSuchBeg # Suchbegriff
fldStatus(as: TEXT) # Status (z.B. "Kunde")
fldInfo # Infofeld
# Direktzugriff auf die Standard-Rechnungs- und Lieferanschrift:
# rowReAnsNr/rowLiAnsNr lösen fldReAnsNr/fldLiAnsNr direkt auf —
# ohne den Umweg über lnkPostalAddresses
fldReAnsNr # Rechnungsanschriftennummer (Referenz)
rowReAnsNr { # → aufgelöste Rechnungsanschrift
fldNa2 # Name / Firma
fldStr # Straße
fldPLZ # PLZ
fldOrt # Ort
}
fldLiAnsNr # Lieferanschriftennummer (Referenz)
rowLiAnsNr { # → aufgelöste Lieferanschrift
fldNa2
fldStr
fldPLZ
fldOrt
}
# Alle Anschriften dieser Adresse (inkl. der oben einzeln abgerufenen)
lnkPostalAddresses {
# Sortierfolge byAdrNrAnsNr: nach Adressnummer und Anschriftnummer
rowsRead(byAdrNrAnsNr: { usingAdrNr: {} }) {
fldAnsNr # Anschriftnummer
fldNa2 # Name / Firma
fldStr # Straße
fldPLZ # Postleitzahl
fldOrt # Ort
# Ansprechpartner dieser Anschrift
lnkContactPeople {
rowsRead(byAdrNrAnsNrAspNr: { usingAdrNrAnsNr: {} }) {
fldAspNr # Ansprechpartnernummer
fldAnr # Anrede
fldVNa # Vorname
fldNNa # Nachname
fldPos # Position / Funktion
fldTel1 # Telefonnummer
fldEMail1 # E-Mail-Adresse
}
}
}
}
}
}
}
Beachten Sie
Wenn die Adressnummer nicht existiert, gibt rowRead den Wert null zurück. Verwenden Sie die Direktive @onNull, um diesen Fall gezielt zu behandeln.
2.2 Paginierte Adressliste
Sobald Sie mehrere Adressen abfragen, verwenden Sie conRead mit Paginierung.
Anders als rowsRead liefert conRead die Ergebnisse seitenweise zurück — das ist der bevorzugte Weg, weil Sie im Voraus nie wissen, wie viele Datensätze eine Abfrage liefert. Ohne first werden alle Datensätze durchlaufen, was bei großen Datenbeständen zu langen Antwortzeiten und hohem Speicherverbrauch führen kann.
# Erste Seite: 3 Adressen lesen, sortiert nach Adressnummer (Standardsortierung)
query AdressenPaginiert {
tblAddresses {
conRead(first: 3) {
# edges enthält die eigentlichen Datensätze als Liste
edges {
cursor # Eindeutiger Cursor dieses Datensatzes (für Paginierung)
node {
fldAdrNr # Adressnummer
fldSuchBeg # Suchbegriff
fldStatus(as: TEXT) # Status als Text (z.B. "Kunde")
}
}
# pageInfo liefert Metadaten zur Paginierung — immer NACH edges abfragen
pageInfo {
hasNextPage # true = es gibt weitere Seiten
endCursor # Cursor des letzten Datensatzes — für die nächste Seite
edgeCount # Anzahl der Datensätze auf dieser Seite
}
}
}
}
Für die nächste Seite übergeben Sie den endCursor aus pageInfo als after-Parameter. Wiederholen Sie dies, bis hasNextPage den Wert false zurückgibt:
# Folgeseite: nächste 3 Adressen ab dem endCursor der vorherigen Abfrage
query AdressenNaechsteSeite {
tblAddresses {
conRead(first: 3, after: "endCursor aus vorheriger pageInfo") {
edges {
node {
fldAdrNr
fldSuchBeg
}
}
pageInfo {
hasNextPage
endCursor
edgeCount
}
}
}
}
Um vorab die Gesamtanzahl zu ermitteln, rufen Sie conRead ohne first und ohne edges auf.
Die API durchläuft dann alle Datensätze, überträgt aber keine Felddaten:
# Nur zählen — keine Datensätze übertragen
query AdressenAnzahl {
tblAddresses {
conRead {
pageInfo {
edgeCount # Gesamtanzahl aller Adressen
}
}
}
}
Tipp
conRead kann dieselben verschachtelten Abfragen wie rowRead enthalten.
Das folgende Beispiel kombiniert die Gesamtzahl mit einer vollständigen Adressabfrage inklusive Anschriften und Ansprechpartnern:
# Paginierte Adressliste mit Anschriften und Ansprechpartnern
query AdressenMitDetails {
# Gesamtanzahl vorab ermitteln (ohne edges, ohne first)
gesamt: tblAddresses {
conRead {
pageInfo { edgeCount } # Gesamtanzahl aller Adressen
}
}
# Erste Seite mit Detaildaten abrufen
tblAddresses {
conRead(first: 5) {
edges {
node {
fldAdrNr # Adressnummer
fldSuchBeg # Suchbegriff
fldStatus(as: TEXT) # Status (z.B. "Kunde")
# Alle Anschriften dieser Adresse über den Link abrufen
lnkPostalAddresses {
rowsRead(byAdrNrAnsNr: { usingAdrNr: {} }) {
fldAnsNr # Anschriftnummer
fldNa2 # Name / Firma
fldStr # Straße
fldPLZ # Postleitzahl
fldOrt # Ort
fldTel # Telefon
fldEMail1 # E-Mail
# Ansprechpartner dieser Anschrift
lnkContactPeople {
rowsRead(byAdrNrAnsNrAspNr: { usingAdrNrAnsNr: {} }) {
fldAspNr # Ansprechpartnernummer
fldAnr # Anrede
fldVNa # Vorname
fldNNa # Nachname
fldPos # Position / Funktion
fldTel1 # Telefon direkt
fldEMail1 # E-Mail direkt
}
}
}
}
}
}
# pageInfo am Ende: Anzahl der Treffer und Cursor für die nächste Seite
pageInfo {
hasNextPage
endCursor
edgeCount # Anzahl der Adressen auf dieser Seite
}
}
}
}
Tipp: Performance: edges vor pageInfo
Fragen Sie edges immer vor pageInfo ab. Die API berechnet pageInfo (insbesondere edgeCount) erst nach dem Durchlaufen der Ergebnismenge.
-
Wenn
edgeszuerst steht, werden die Datensätze dabei direkt ausgeliefert. -
Steht
pageInfozuerst, muss die Ergebnismenge zweimal durchlaufen werden.
2.3 Adressen filtern
Die API bietet drei Filtermechanismen mit unterschiedlicher Performance. Wählen Sie immer den schnellsten, der Ihren Anforderungen genügt.
Sortierfolge mit Schlüsselfilter — Schnellster Zugriff
Über allBetween wählen Sie eine Sortierfolge und schränken den Bereich über Schlüsselfelder (kf1...) ein. Die Abfrage nutzt den Index direkt und ist daher am schnellsten.
Ohne edges und ohne first erhalten Sie nur die Anzahl der Treffer — ideal, um vorab zu prüfen, wie viele Datensätze eine Filterung liefert:
# Nur die Anzahl aller Kunden ermitteln — keine Datensätze übertragen
query AnzahlKunden {
tblAddresses {
conRead(allBetween: {
byStatus: {
kf1Status: { text: "Kunde" }
}
}) {
pageInfo {
edgeCount # Anzahl aller Kunden
}
}
}
}
Mit edges und first rufen Sie die eigentlichen Datensätze seitenweise ab:
# Erste 10 Kunden mit Daten abrufen
query AlleKunden {
tblAddresses {
conRead(first: 10, allBetween: {
byStatus: {
kf1Status: { text: "Kunde" }
}
}) {
edges {
node {
fldAdrNr # Adressnummer
fldSuchBeg # Suchbegriff
fldStatus(as: TEXT) # Status — hier immer "Kunde"
}
}
pageInfo { hasNextPage endCursor edgeCount }
}
}
}
Mit from/to auf dem Schlüsselfeld können Sie Bereichsabfragen über den Index durchführen — beispielsweise alle Adressen, deren Suchbegriff mit "MÜLLER" beginnt. Die API liest dabei nur den relevanten Indexbereich, nicht die gesamte Tabelle. Details zu Sortierfolgen und Indexstrukturen finden Sie in der GraphQL Doku - Abfragen (Queries).
# Präfixsuche — alle Adressen mit Suchbegriff "MÜLLER..."
query SuchbegriffMueller {
tblAddresses {
conRead(first: 10, allBetween: {
bySuchBeg: {
# Bereich von "MÜLLER" bis "MÜLLERZZZ" erfasst alle Präfix-Treffer
kf1SuchBeg: {
from: { text: "MÜLLER" }
to: { text: "MÜLLERZZZ" }
}
}
}) {
edges {
node {
fldAdrNr
fldSuchBeg # z.B. "MÜLLER MEIER", "MÜLLER-LÜDENSCHEID"
}
}
pageInfo { hasNextPage endCursor edgeCount }
}
}
}
fastFilter — Schneller Feldwertfilter
fastFilter lässt sich mit einer Sortierfolge kombinieren, um die Ergebnismenge zusätzlich einzuschränken. Verfügbare Felder sind im Enum AddressFastAnyFields definiert.
# Alle Kunden mit Suchbegriff "M..." — Sortierfolge + fastFilter kombiniert
query KundenMitSuchbegriffM {
tblAddresses {
conRead(first: 10,
# Sortierfolge bySuchBeg: nur Suchbegriffe von "M" bis "MZZZ"
allBetween: { bySuchBeg: { kf1SuchBeg: {
from: { text: "M" }
to: { text: "MZZZ" }
} } },
# fastFilter schränkt zusätzlich auf Status "Kunde" ein
fastFilter: { eq: [{ field: fldStatus }, { value: "Kunde" }] }
) {
edges {
node {
fldAdrNr
fldSuchBeg # z.B. "MAIER", "MICROTECH", "MÜLLER-LÜDENSCHEID"
fldStatus(as: TEXT) # immer "Kunde"
}
}
pageInfo { hasNextPage edgeCount }
}
}
}
slowFilter — Flexibler Filter
slowFilter prüft jeden Datensatz einzeln und ist daher langsamer, bietet aber volle Flexibilität. Über Funktionen wie fnPos (Teilstring-Suche) oder fnLeft/fnRight können Sie Bedingungen formulieren, die mit fastFilter nicht möglich sind.
# Teilstring-Suche — alle Adressen, die "MÜLLER" irgendwo im Suchbegriff enthalten
query AdressenMitSlowFilter {
tblAddresses {
conRead(first: 10, slowFilter: {
# fnPos("MÜLLER", fldSuchBeg) > 0 — prüft ob "MÜLLER" im Feld vorkommt
gt: [
{ fnPos: [{ value: "MÜLLER" }, { field: fldSuchBeg }] },
{ value: 0 }
]
}) {
edges {
node {
fldAdrNr
fldSuchBeg # z.B. "MÜLLER MEIER", "MÜLLER-LÜDENSCHEID"
fldStatus(as: TEXT)
}
}
pageInfo { hasNextPage edgeCount }
}
}
}
Performance-Reihenfolge
Sortierfolge mit keyFilter (nutzt Index) > fastFilter (Feldvergleich) > slowFilter (prüft jeden Datensatz). Verwenden Sie slowFilter nur, wenn kein passender Schlüsselzugriff oder fastFilter ausreicht.
3. Adresse mit Anschrift und Ansprechpartner anlegen
Das Anlegen einer Adresse mit Anschrift und Ansprechpartner erfolgt als verschachtelte Mutation. In einer einzigen Operation wird die Adresse erstellt, gespeichert, dann über den Link eine Anschrift angelegt und gespeichert, und schließlich über einen weiteren Link ein Ansprechpartner hinzugefügt.
Ablauf der verschachtelten Mutation
rowNew (Adresse)
└─ Felder setzen (fldSuchBeg, fldStatus, ...)
└─ rowSave
└─ lnkPostalAddresses
└─ rowNew (Anschrift) mit setAdrNrAnsNr: usingAdrNr
└─ Felder setzen (fldNa2, fldStr, fldPLZ, fldOrt)
└─ rowSave
└─ lnkContactPeople
└─ rowNew (Ansprechpartner) mit setAdrNrAnsNrNa: usingAdrNrAnsNr
└─ Felder setzen (fldAnr, fldVNa, fldNNa, ...)
└─ rowSave
Der Parameter setAdrNrAnsNr: usingAdrNr sorgt dafür, dass die neue Anschrift automatisch die fldAdrNr der übergeordneten Adresse übernimmt. Analog setzt setAdrNrAnsNrNa: usingAdrNrAnsNr beim Ansprechpartner automatisch Adressnummer und Anschriftnummer.
Beachten Sie
Jeder rowNew-Block muss ein rowSave enthalten, damit der Datensatz tatsächlich in die Datenbank geschrieben wird. Dies gilt insbesondere auch für den innersten Ansprechpartner — ein rowNew ohne rowSave verwirft den Datensatz.
Neuanlegen von Adresse mit Anschrift und Ansprechpartner
mutation AdresseMitAnschriftUndAnsprechpartnerAnlegen
@acquireLocks(forWriting: [tblAddresses, tblPostalAddresses, tblContactPeople])
{
tblAddresses {
# Neue Adresse anlegen
rowNew {
fldSuchBeg(set: { text: "BEISPIEL GMBH" }) # Suchbegriff
fldStatus(set: { text: "Kunde" }) # Adressstatus — bestimmt den Nummernkreis
# Adresse speichern — liefert RowMutationRead zurück
rowSave {
fldAdrNr # Vom System vergebene Adressnummer
# Anschrift über Link anlegen
lnkPostalAddresses {
# setAdrNrAnsNr: usingAdrNr übernimmt die AdrNr automatisch
rowNew(setAdrNrAnsNr: usingAdrNr) {
fldNa2(set: { text: "Beispiel GmbH" }) # Name / Firma
fldStr(set: { text: "Industriestraße 42" }) # Straße
fldPLZ(set: { text: "60311" }) # Postleitzahl
fldOrt(set: { text: "Frankfurt am Main" }) # Ort
fldStdReKz(set: { boolean: true }) # Als Standard-Rechnungsanschrift setzen
# Anschrift speichern
rowSave {
fldAnsNr # Vom System vergebene Anschriftnummer
# Ansprechpartner über Link anlegen
lnkContactPeople {
# setAdrNrAnsNrNa: usingAdrNrAnsNr übernimmt AdrNr und AnsNr
rowNew(setAdrNrAnsNrNa: usingAdrNrAnsNr) {
fldAnr(set: { text: "Herr" }) # Anrede
fldVNa(set: { text: "Thomas" }) # Vorname
fldNNa(set: { text: "Schmidt" }) # Nachname
fldPos(set: { text: "Einkauf" }) # Position
fldTel1(set: { text: "069 12345678" }) # Telefon
fldEMail1(set: { text: "t.schmidt@beispiel.de" }) # E-Mail
# Ansprechpartner speichern
rowSave {
fldAspNr # Vom System vergebene Nummer
}
}
}
}
}
}
}
}
}
}
Adressstatus und Nummernkreise
Der fldStatus bestimmt den Nummernkreis für die automatisch vergebene fldAdrNr — z. B. erhalten Kunden eine Nummer ab 10000, Lieferanten eine Nummer ab 70000. Die verfügbaren Statuswerte (Interessent, Kunde, Lieferant etc.) sind in tblAddressStatuses konfiguriert. Siehe Parameter - Auslesen: 3.1 Adressstatus.
Beachten Sie
Die Direktive @acquireLocks(forWriting: [...]) fordert Schreibsperren für die angegebenen Tabellen zu Beginn der Transaktion an. Ohne diese Direktive werden Sperren erst beim tatsächlichen Schreibzugriff gesetzt, was zu Deadlocks führen kann, wenn parallel andere Transaktionen auf dieselben Tabellen zugreifen. Weitere Details finden Sie Kapitel zu den Mutations unter folgendem Punkt: Wann @acquireLocks verwenden?.
4. Adresse mit Anschrift und Ansprechpartner ändern
Zum Ändern navigieren Sie per rowModify zur gewünschten Adresse, setzen die neuen Feldwerte und speichern. Über die Links können Sie in derselben Mutation auch Anschriften und Ansprechpartner ändern. Es muss dabei nicht auf jeder Ebene etwas geändert werden — Sie können frei wählen, welche Daten Sie aktualisieren.
4.1 Nur Adressdaten ändern
Wenn nur Felder der Adresse selbst geändert werden sollen, genügt rowModify ohne verschachtelte Links:
mutation AdresseAendern {
tblAddresses {
rowModify(kf1AdrNr: { text: "10001" }) {
fldSuchBeg(set: { text: "STEFFIS TINTENFASS NEU" }) # Suchbegriff aktualisieren
fldInfo(set: { text: "Stammkunde seit 2020" }) # Infofeld aktualisieren
rowSave {
fldAdrNr
fldSuchBeg # Aktualisierter Wert
fldInfo # Aktualisierter Wert
}
}
}
}
4.2 Nur Anschrift ändern
Wenn Sie nur eine Anschrift ändern möchten, greifen Sie direkt über tblPostalAddresses zu. Der zusammengesetzte Schlüssel besteht aus kf1AdrNr (Adressnummer) und kf2AnsNr (Anschriftnummer):
mutation AnschriftDirektAendern {
tblPostalAddresses {
# Direktzugriff: Adresse 10001, Anschrift Nr. 1
rowModify(kf1AdrNr: { text: "10001", kf2AnsNr: { int: 1 } }) {
fldStr(set: { text: "Bahnhofstraße 14" }) # Neue Straße
fldOrt(set: { text: "Marburg" }) # Neuer Ort
rowSave {
fldAdrNr # Bestätigung
fldAnsNr
fldStr # Aktualisierter Wert
fldOrt # Aktualisierter Wert
}
}
}
}
Rechnungs- und Lieferanschrift ändern
Über die Adresse können Sie per usingAdrNrReAnsNr bzw. usingAdrNrLiAnsNr direkt auf die hinterlegte Rechnungs- oder Lieferanschrift zugreifen — ohne die Anschriftnummer vorher nachschlagen zu müssen:
# Rechnungsanschrift ändern — usingAdrNrReAnsNr löst fldReAnsNr automatisch auf
mutation RechnungsanschriftAendern {
tblAddresses {
rowRead(kf1AdrNr: { text: "10001" }) {
fldReAnsNr # Zur Kontrolle: welche AnsNr ist hinterlegt?
lnkPostalAddresses {
# usingAdrNrReAnsNr: navigiert direkt zur Rechnungsanschrift
rowsModify(byAdrNrAnsNr: { usingAdrNrReAnsNr: {} }) {
fldStr(set: { text: "Rheingasse 1B" })
rowSave {
fldAnsNr # Bestätigung der Anschriftnummer
fldNa2
fldStr
fldPLZ
fldOrt
}
}
}
}
}
}
# Lieferanschrift ändern — usingAdrNrLiAnsNr löst fldLiAnsNr automatisch auf
mutation LieferanschriftAendern {
tblAddresses {
rowRead(kf1AdrNr: { text: "10001" }) {
fldLiAnsNr # Zur Kontrolle: welche AnsNr ist hinterlegt?
lnkPostalAddresses {
# usingAdrNrLiAnsNr: navigiert direkt zur Lieferanschrift
rowsModify(byAdrNrAnsNr: { usingAdrNrLiAnsNr: {} }) {
fldStr(set: { text: "Industriepark 7" })
rowSave {
fldAnsNr
fldNa2
fldStr
fldPLZ
fldOrt
}
}
}
}
}
}
Wann rowReAnsNr/rowLiAnsNr, wann lnkPostalAddresses?
-
rowReAnsNr/rowLiAnsNr: Wenn die Standard-Rechnungs- oder Lieferanschrift gelesen werden soll. LöstfldReAnsNr/fldLiAnsNrdirekt auf — schnell und ohne Sortierfolge. Nur lesend verfügbar. -
lnkPostalAddresses: Wenn Anschriften geändert, hinzugefügt oder gelöscht werden sollen. MitusingAdrNrReAnsNr/usingAdrNrLiAnsNrgezielt die Standard-Anschrift, mitusingAdrNralle Anschriften der Adresse.
4.3 Nur Ansprechpartner ändern
Auch Ansprechpartner können direkt über tblContactPeople geändert werden. Der zusammengesetzte Schlüssel besteht aus drei Teilen — kf1AdrNr, kf2AnsNr und kf3AspNr:
mutation AnsprechpartnerDirektAendern {
tblContactPeople {
# Direktzugriff: Adresse 10001, Anschrift Nr. 1, Ansprechpartner Nr. 1
rowModify(kf1AdrNr: { text: "10001", kf2AnsNr: { int: 1, kf3AspNr: { int: 1 } } }) {
fldTel1(set: { text: "06421 98765" }) # Neue Telefonnummer
fldEMail1(set: { text: "neu@beispiel.de" }) # Neue E-Mail
rowSave {
fldAdrNr
fldAnsNr
fldAspNr
fldTel1
fldEMail1
}
}
}
}
Standard-Ansprechpartner einer Anschrift ändern
Jede Anschrift hat einen Standard-Ansprechpartner, referenziert über fldAspNr. Über tblPostalAddresses navigieren Sie zur Anschrift und ändern von dort per lnkContactPeople mit usingAdrNrAnsNrAspNr den Standard-Ansprechpartner — ohne dessen Nummer vorher nachschlagen zu müssen:
mutation StandardAnsprechpartnerAendern {
tblPostalAddresses {
# Anschrift öffnen (nur lesen — Anschrift selbst wird nicht geändert)
rowRead(kf1AdrNr: { text: "10001", kf2AnsNr: { int: 1 } }) {
fldAspNr # Zur Kontrolle: welcher ASP ist Standard?
lnkContactPeople {
# usingAdrNrAnsNrAspNr: löst fldAspNr der Anschrift automatisch auf
# → ändert gezielt den Standard-ASP, nicht alle Ansprechpartner
# (usingAdrNrAnsNr: {} ohne AspNr würde ALLE Ansprechpartner ändern)
rowsModify(byAdrNrAnsNrAspNr: { usingAdrNrAnsNrAspNr: {} }) {
fldTel1(set: { text: "069 12345678" }) # Neue Telefonnummer
rowSave {
fldAspNr
fldVNa
fldNNa
fldTel1
}
}
}
}
}
}
Wann rowAspNr, wann lnkContactPeople?
-
rowAspNr: Wenn der Standard-Ansprechpartner einer Anschrift gelesen werden soll. LöstfldAspNrdirekt auf — schnell und ohne Sortierfolge. Nur lesend verfügbar. -
lnkContactPeople: Wenn Ansprechpartner geändert, hinzugefügt oder gelöscht werden sollen. MitusingAdrNrAnsNrAspNrgezielt den Standard-Ansprechpartner, mitusingAdrNrAnsNralle Ansprechpartner der Anschrift.
4.4 Alle Ebenen gleichzeitig ändern
Im komplexesten Fall können Sie mit nur einer einzigen Mutation sowohl Adresse, Standard-Rechnungsanschrift und deren Standard-Ansprechpartner ändern. usingAdrNrReAnsNr navigiert automatisch zur Rechnungsanschrift, usingAdrNrAnsNrAspNr zum Standard-Ansprechpartner dieser Anschrift:
mutation AdresseMitReAnschriftUndStdAspAendern
@acquireLocks(forWriting: [tblAddresses, tblPostalAddresses, tblContactPeople])
{
tblAddresses {
# Adresse zur Bearbeitung öffnen
rowModify(kf1AdrNr: { text: "10001" }) {
fldInfo(set: { text: "Stammkunde seit 2020" }) # Infofeld aktualisieren
# Adresse speichern
rowSave {
fldAdrNr # Bestätigung der Adressnummer
fldInfo # Aktualisierter Wert
# Standard-Rechnungsanschrift ändern
lnkPostalAddresses {
# usingAdrNrReAnsNr: löst fldReAnsNr automatisch auf
rowsModify(byAdrNrAnsNr: { usingAdrNrReAnsNr: {} }) {
fldStr(set: { text: "Bahnhofstraße 14" }) # Neue Straße
rowSave {
fldAnsNr # Bestätigung
fldStr # Aktualisierter Wert
# Standard-Ansprechpartner dieser Anschrift ändern
lnkContactPeople {
# usingAdrNrAnsNrAspNr: löst fldAspNr der Anschrift automatisch auf
rowsModify(byAdrNrAnsNrAspNr: { usingAdrNrAnsNrAspNr: {} }) {
fldTel1(set: { text: "06421 98765" }) # Neue Telefonnummer
rowSave {
fldAspNr
fldTel1 # Aktualisierter Wert
}
}
}
}
}
}
}
}
}
}
Beachten Sie
Bei verschachtelten Änderungen über Links werden assignAddress und assignPostalAddress nicht benötigt.
Diese Parameter sind nur beim eigenständigen Zugriff auf tblPostalAddresses oder tblContactPeople erforderlich (siehe Abschnitt 6.5).
5. Adresse löschen
Das Löschen einer Adresse erfolgt über rowDelete. Optional kann der Parameter ignoreWarnings angegeben werden, um Warnmeldungen zu unterdrücken.
mutation AdresseLoeschen {
tblAddresses {
# Adresse per Adressnummer löschen
# ignoreWarnings: true unterdrückt Warnungen bei bestehenden Abhängigkeiten
# Ohne diesen Parameter erhalten Sie Hinweise, falls z.B. Vorgänge referenziert werden
rowDelete(kf1AdrNr: { text: "10005" }, ignoreWarnings: true) {
fldAdrNr # Bestätigung: welche Adresse wurde gelöscht
fldSuchBeg # Suchbegriff — zur Kontrolle im Rückgabewert
}
}
}
Beachten Sie
Das Löschen einer Adresse kann kaskadierende Auswirkungen haben, d. h. weitere aufeinanderfolgende Effekte.
Prüfen Sie vor dem Löschen, ob die Adresse in Vorgängen, Offenen Posten oder anderen Bereichen referenziert wird. Ohne ignoreWarnings: true erhalten Sie Warnmeldungen, die auf bestehende Abhängigkeiten hinweisen.
6. Erweiterte Muster
6.1 Optimistic Locking (modifyLSN)
Wenn mehrere Benutzer oder Systeme gleichzeitig auf Adressen zugreifen, kann es zu Konflikten kommen. Das Konzept des Optimistic Locking verhindert, dass versehentlich Änderungen eines anderen Benutzers überschrieben werden.
Der Ablauf ist zweistufig:
- Beim Lesen den aktuellen
fldModifyLSN-Wert merken - Beim Schreiben den gemerkten Wert als
modifyLSNübergeben
Stimmt der Wert nicht mehr überein (weil ein anderer Benutzer den Datensatz zwischenzeitlich geändert hat), wird die Änderung übersprungen. Über eine Variable und @include können Sie in derselben Mutation den aktuellen Stand nachlesen.
# Schritt 1: Adresse lesen und LSN merken
query AdresseMitLSN {
tblAddresses {
rowRead(kf1AdrNr: { text: "10001" }) {
fldAdrNr
fldSuchBeg
fldModifyLSN # Diesen Wert für die Änderung merken
}
}
}
# Schritt 2: Adresse ändern mit LSN-Prüfung
mutation AdresseAendernMitLSN(
$adrNr: String = "10001"
$modifyLSN: BigInt = 9161168561880151768 # Wert aus Schritt 1
$notFound: Boolean! = false # Steuervariable für den Konfliktfall
) {
tblAddresses {
# Änderung nur durchführen, wenn modifyLSN übereinstimmt
modified: rowModify(
kf1AdrNr: { text: $adrNr }
modifyLSN: $modifyLSN
)
@onNull(
returnValue: skip # Bei LSN-Mismatch: Änderung überspringen
set: { var: $notFound } # $notFound auf true setzen
)
{
fldSuchBeg(set: { text: "STEFFIS TINTENFASS NEU" })
rowSave {
fldModifyLSN # Neuer LSN-Wert nach der Änderung
}
}
# Nur bei Konflikt: aktuellen Stand nachlesen
mismatchedLSN: rowRead(
kf1AdrNr: { text: $adrNr }
)
@include(if: $notFound) # Wird nur ausgeführt wenn LSN nicht passte
{
fldAdrNr
fldSuchBeg
fldModifyLSN # Aktueller LSN — für erneuten Versuch
}
}
}
Info
Der modifyLSN-Wert ist ein Beispiel. Verwenden Sie immer den tatsächlichen Wert aus der vorherigen Leseabfrage. Bei LSN-Mismatch liefert modified den Wert null (durch returnValue: skip) und mismatchedLSN den aktuellen Datensatz mit dem neuen fldModifyLSN — damit kann der Client den Konflikt erkennen und erneut versuchen.
6.2 Bedingte Erstellung (ifNotExists)
Mit dem Parameter ifNotExists können Sie eine Adresse nur dann anlegen, wenn sie noch nicht existiert. Über @onNull mit einer Steuervariable und @include können Sie in derselben Mutation den bestehenden Datensatz nachlesen.
mutation AdresseNurWennNichtVorhanden(
$adrNr: String = "10001"
$exists: Boolean! = false # Steuervariable für den Existenzfall
) {
tblAddresses {
# Adresse nur anlegen, wenn AdrNr noch nicht existiert
created: rowNew(ifNotExists: { kf1AdrNr: { text: $adrNr } })
@onNull(
returnValue: skip # Bei Existenz: Anlage überspringen
set: { var: $exists } # $exists auf true setzen
)
{
fldSuchBeg(set: { text: "NEUER KUNDE" })
rowSave {
fldAdrNr
fldSuchBeg
}
}
# Nur wenn Adresse bereits existiert: bestehenden Datensatz nachlesen
existing: rowRead(
kf1AdrNr: { text: $adrNr }
)
@include(if: $exists) # Wird nur ausgeführt wenn Adresse schon da war
{
fldAdrNr
fldSuchBeg
fldStatus(as: TEXT)
}
}
}
Wenn die Adresse bereits existiert, liefert created den Wert null (durch returnValue: skip) und existing den bestehenden Datensatz. So kann der Client erkennen, ob die Adresse neu angelegt oder bereits vorhanden war.
6.3 Datensatz kopieren (rowCopy)
Mit rowCopy erstellen Sie eine Kopie eines bestehenden Datensatzes. Die Kopie kann vor dem Speichern noch angepasst werden.
mutation AdresseKopieren {
tblAddresses {
# rowCopy erstellt eine Kopie von Adresse 10001
# Nicht explizit gesetzte Felder behalten die Werte des Originals
rowCopy(kf1AdrNr: { text: "10001" }) {
# Nur den Suchbegriff ändern — alle anderen Felder werden vom Original übernommen
fldSuchBeg(set: { text: "KOPIE VON STEFFIS TINTENFASS" })
rowSave {
fldAdrNr # Neue, vom System vergebene Adressnummer
fldSuchBeg # Angepasster Suchbegriff
fldStatus(as: TEXT) # Status — vom Original übernommen
}
}
}
}
6.4 Massenoperationen (rowsModify, rowsDelete)
Für die Änderung oder Löschung mehrerer Datensätze stehen rowsModify und rowsDelete zur Verfügung. Diese Operationen arbeiten auf allen Datensätzen, die der angegebenen Sortierfolge oder dem Filter entsprechen.
Mehrere Anschriften einer Adresse ändern
mutation AnschriftenMassenAenderung {
tblAddresses {
# Adresse per rowRead öffnen (nur lesen — Adresse selbst wird nicht geändert)
rowRead(kf1AdrNr: { text: "10001" }) {
# Über den Link alle Anschriften dieser Adresse erreichen
lnkPostalAddresses {
# rowsModify wirkt auf ALLE Datensätze der Sortierfolge
# usingAdrNr: {} übernimmt die AdrNr aus dem übergeordneten rowRead
rowsModify(byAdrNrAnsNr: { usingAdrNr: {} }) {
fldOrt(set: { text: "Marburg an der Lahn" }) # Ort bei ALLEN Anschriften ändern
rowSave {
fldAnsNr # Welche Anschrift wurde geändert
fldOrt # Aktualisierter Wert zur Kontrolle
}
}
}
}
}
}
Mehrere Ansprechpartner löschen
mutation AnsprechpartnerMassenLoeschung {
tblAddresses {
# Navigation: Adresse → Anschrift Nr. 1 → alle Ansprechpartner löschen
rowRead(kf1AdrNr: { text: "10001" }) {
lnkPostalAddresses {
# Nur Anschrift Nr. 1 auswählen (kf2AnsNr innerhalb usingAdrNr verschachtelt)
rowsRead(byAdrNrAnsNr: {
usingAdrNr: {
kf2AnsNr: { int: 1 }
}
}) {
lnkContactPeople {
# rowsDelete löscht ALLE Ansprechpartner dieser Anschrift
# usingAdrNrAnsNr: {} übernimmt AdrNr + AnsNr aus dem Kontext
rowsDelete(byAdrNrAnsNrAspNr: {
usingAdrNrAnsNr: {}
}, ignoreWarnings: true) {
fldAspNr # Nummer des gelöschten Ansprechpartners
fldNNa # Nachname — zur Kontrolle im Rückgabewert
}
}
}
}
}
}
}
6.5 assign-Parameter
Die Parameter assignAddress und assignPostalAddress werden benötigt, wenn Sie eigenständig (nicht über Links) auf tblPostalAddresses oder tblContactPeople zugreifen. Sie stellen die Zuordnung zur übergeordneten Adresse bzw. Anschrift her.
| Parameter | Tabelle | Zweck |
|---|---|---|
assignAddress | tblPostalAddresses | Verknüpft die Anschrift mit einer Adresse |
assignPostalAddress | tblContactPeople | Verknüpft den Ansprechpartner mit einer Anschrift |
Beim verschachtelten Zugriff über Links sind diese Parameter nicht erforderlich, da die Zuordnung automatisch über den Kontext der übergeordneten Tabelle erfolgt. Ebenso wenig beim Direktzugriff über tblPostalAddresses oder tblContactPeople mit vollständigem Schlüssel (kf1AdrNr + kf2AnsNr + ggf. kf3AspNr), da der Datensatz dadurch eindeutig identifiziert ist.
Anschrift eigenständig anlegen (assignAddress)
mutation AnschriftEigenstaendigAnlegen {
tblPostalAddresses {
# assignAddress: Verknüpft die neue Anschrift mit Adresse 10001
rowNew(assignAddress: { kf1AdrNr: { text: "10001" } }) {
fldNa2(set: { text: "Zweigstelle Nord" })
fldStr(set: { text: "Hafenstraße 12" })
fldPLZ(set: { text: "20095" })
fldOrt(set: { text: "Hamburg" })
rowSave {
fldAdrNr # Übernommen aus assignAddress
fldAnsNr # Vom System vergeben
fldNa2
fldStr
fldPLZ
fldOrt
}
}
}
}
Ansprechpartner eigenständig anlegen (assignPostalAddress)
mutation AnsprechpartnerEigenstaendigAnlegen {
tblContactPeople {
# assignPostalAddress: Verknüpft den neuen ASP mit Adresse 10001, Anschrift 1
rowNew(assignPostalAddress: {
kf1AdrNr: { text: "10001", kf2AnsNr: { int: 1 } }
}) {
fldAnr(set: { text: "Frau" })
fldVNa(set: { text: "Lisa" })
fldNNa(set: { text: "Weber" })
fldTel1(set: { text: "040 9876543" })
fldEMail1(set: { text: "l.weber@beispiel.de" })
rowSave {
fldAdrNr # Übernommen aus assignPostalAddress
fldAnsNr # Übernommen aus assignPostalAddress
fldAspNr # Vom System vergeben
fldVNa
fldNNa
}
}
}
}
Tipp
Bevorzugen Sie den verschachtelten Zugriff über Links (lnkPostalAddresses, lnkContactPeople). Der eigenständige Tabellenzugriff mit assign-Parametern ist nur dann sinnvoll, wenn Sie gezielt einzelne Anschriften oder Ansprechpartner ohne den Kontext der übergeordneten Adresse anlegen müssen — z.B. wenn die übergeordnete Adresse in derselben Mutation nicht verarbeitet wird.
7. Tipps und Stolpersteine
-
keyFilter vor slowFilter: Verwenden Sie für den Zugriff auf Adressen immer den Schlüsselzugriff
kf1AdrNr, wenn die Adressnummer bekannt ist. Dies ist der schnellste Zugriffspfad.slowFilterdurchsucht jeden Datensatz einzeln und ist deutlich langsamer. -
rowSave nicht vergessen: Jeder schreibende Row-Kontext (
rowNew,rowModify,rowCopy) muss mitrowSaveabgeschlossen werden. OhnerowSavewerden die Änderungen verworfen — auch bei verschachtelten Ansprechpartnern. -
Sperren vorab anfordern: Verwenden Sie
@acquireLocks(forWriting: [...])bei Mutationen, die mehrere Tabellen betreffen. Ohne vorab angeforderte Sperren können Deadlocks auftreten. -
Gesamtanzahl ermitteln:
conReadohnefirst-Parameter liefertedgeCountüber alle Datensätze. Für die Gesamtanzahl:conRead { pageInfo { edgeCount } }ohneedges. -
edges vor pageInfo: Fragen Sie
edgesimmer vorpageInfoab — das ist performanter, da die APIpageInfoerst nach dem Durchlaufen der Ergebnismenge berechnet. -
Sortierfolgen über Links: Innerhalb von Links stehen andere Sortierfolgen zur Verfügung als auf Tabellenebene.
lnkPostalAddressesbietetbyAdrNrAnsNrundbyAdrNrNa2,lnkContactPeoplebietetbyAdrNrAnsNrAspNr,byAdrNrAnsNrNaundbyAdrNrAnsNrNNa. -
Enum-Werte bei rowNew über Links: Beim Anlegen über Links müssen Sie den passenden
set...-Enum angeben, damit die Schlüsselfelder automatisch aus dem übergeordneten Kontext übernommen werden:- Anschrift:
setAdrNrAnsNr: usingAdrNr - Ansprechpartner:
setAdrNrAnsNrNa: usingAdrNrAnsNr
- Anschrift:
-
Adressnummer automatisch vergeben: Wenn Sie beim Anlegen einer Adresse keine
fldAdrNrsetzen, vergibt das System automatisch die nächste freie Nummer. -
Verschachteltes Anlegen nur nach rowSave: Links (
lnk...) sind erst nachrowSavedes übergeordneten Datensatzes verfügbar. Die Anschrift kann erst angelegt werden, nachdem die Adresse gespeichert wurde. -
Transaktionale Sicherheit: Die gesamte verschachtelte Mutation wird atomar ausgeführt. Schlägt ein Teilschritt fehl (z.B. Ansprechpartner kann nicht gespeichert werden), werden alle Änderungen zurückgerollt — auch die bereits gespeicherte Adresse und Anschrift.
-
Standard-Anschrift und Standard-Ansprechpartner per Shortcut: Über Links stehen Shortcuts zur Verfügung, die hinterlegte Referenznummern automatisch auflösen:
usingAdrNrReAnsNr/usingAdrNrLiAnsNr→ navigiert zur Standard-Rechnungs-/Lieferanschrift (fldReAnsNr/fldLiAnsNr)usingAdrNrAnsNrAspNr→ navigiert zum Standard-Ansprechpartner einer Anschrift (fldAspNr)- Zum Lesen gibt es zusätzlich die Direktfelder
rowReAnsNr,rowLiAnsNrundrowAspNr.
-
rowSaveAndModify: Nach
rowSavesteht auchrowSaveAndModifyzur Verfügung, das den Datensatz speichert und sofort wieder zur Bearbeitung öffnet. Dies ist nützlich, wenn Sie nach dem Speichern weitere Änderungen am selben Datensatz vornehmen möchten.