Zum Inhalt

Praxisbeispiel: Adressen, Anschriften und Ansprechpartner verwalten (GraphQL)

Gen. 24 Enterprise

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

  1. Überblick
  2. Adressen lesen
  3. Adresse mit Anschrift und Ansprechpartner anlegen
  4. Adresse mit Anschrift und Ansprechpartner ändern
  5. Adresse löschen
  6. Erweiterte Muster
  7. 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, tblPostalAddresses und tblContactPeople erreichbar.

  • 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 edges zuerst steht, werden die Datensätze dabei direkt ausgeliefert.

  • Steht pageInfo zuerst, 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öst fldReAnsNr/fldLiAnsNr direkt auf — schnell und ohne Sortierfolge. Nur lesend verfügbar.

  • lnkPostalAddresses: Wenn Anschriften geändert, hinzugefügt oder gelöscht werden sollen. Mit usingAdrNrReAnsNr/usingAdrNrLiAnsNr gezielt die Standard-Anschrift, mit usingAdrNr alle 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öst fldAspNr direkt auf — schnell und ohne Sortierfolge. Nur lesend verfügbar.

  • lnkContactPeople: Wenn Ansprechpartner geändert, hinzugefügt oder gelöscht werden sollen. Mit usingAdrNrAnsNrAspNr gezielt den Standard-Ansprechpartner, mit usingAdrNrAnsNr alle 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:

  1. Beim Lesen den aktuellen fldModifyLSN-Wert merken
  2. 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

  1. 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. slowFilter durchsucht jeden Datensatz einzeln und ist deutlich langsamer.

  2. rowSave nicht vergessen: Jeder schreibende Row-Kontext (rowNew, rowModify, rowCopy) muss mit rowSave abgeschlossen werden. Ohne rowSave werden die Änderungen verworfen — auch bei verschachtelten Ansprechpartnern.

  3. Sperren vorab anfordern: Verwenden Sie @acquireLocks(forWriting: [...]) bei Mutationen, die mehrere Tabellen betreffen. Ohne vorab angeforderte Sperren können Deadlocks auftreten.

  4. Gesamtanzahl ermitteln: conRead ohne first-Parameter liefert edgeCount über alle Datensätze. Für die Gesamtanzahl: conRead { pageInfo { edgeCount } } ohne edges.

  5. edges vor pageInfo: Fragen Sie edges immer vor pageInfo ab — das ist performanter, da die API pageInfo erst nach dem Durchlaufen der Ergebnismenge berechnet.

  6. Sortierfolgen über Links: Innerhalb von Links stehen andere Sortierfolgen zur Verfügung als auf Tabellenebene. lnkPostalAddresses bietet byAdrNrAnsNr und byAdrNrNa2, lnkContactPeople bietet byAdrNrAnsNrAspNr, byAdrNrAnsNrNa und byAdrNrAnsNrNNa.

  7. 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
  8. Adressnummer automatisch vergeben: Wenn Sie beim Anlegen einer Adresse keine fldAdrNr setzen, vergibt das System automatisch die nächste freie Nummer.

  9. Verschachteltes Anlegen nur nach rowSave: Links (lnk...) sind erst nach rowSave des übergeordneten Datensatzes verfügbar. Die Anschrift kann erst angelegt werden, nachdem die Adresse gespeichert wurde.

  10. 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.

  11. 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, rowLiAnsNr und rowAspNr.
  12. rowSaveAndModify: Nach rowSave steht auch rowSaveAndModify zur 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.