# GraphQL Entwickler Dokumentation - Mutationen (Mutations)

<span class="custom-button red-button">Gen. 24 Enterprise</span>

Diese Seite knüpft direkt an die **[GraphQL Entwickler Dokumentation - Abfragen (Queries)](../18_graphql/graphqldoku.md)** an und beschreibt die in GraphQL nutzbaren Mutationen.

## 11. Grundlagen der Mutationen

### 11.1 Einführung in GraphQL-Mutationen

In der GraphQL-Spezifikation existieren zwei primäre Operationstypen: `query` für Leseoperationen und `mutation` für Datenmanipulation. Die microtech GraphQL-Implementierung folgt diesem Standard, erweitert ihn jedoch um eine besondere Eigenschaft: Mutationen sind hier als vollständiges Superset von Abfragen realisiert.

Dies bedeutet konkret: Jede gültige `query`-Operation kann durch einfaches Ersetzen des Schlüsselworts in eine `mutation`-Operation umgewandelt werden. Der fundamentale Unterschied liegt nicht in der verfügbaren Syntax, sondern im zugrundeliegenden Transaktionsmodell und den zusätzlich verfügbaren Schreiboperationen.

```graphql
# Diese Abfrage...
query {
  tblProducts {
    rowsRead {
      fldArtNr
      fldSuchBeg
    }
  }
}

# ...funktioniert identisch als Mutation
mutation {
  tblProducts {
    rowsRead {
      fldArtNr
      fldSuchBeg
    }
  }
}
```

### 11.2 Das Transaktionsmodell im Detail

#### 11.2.1 Snapshot-Transaktionen bei Abfragen

`query`-Operationen werden in speziellen Snapshot-Transaktionen ausgeführt. Diese bieten eine konsistente Sicht auf die Datenbank zum Zeitpunkt des Transaktionsbeginns, ohne dass Sperren gesetzt werden müssen. Alle Lesezugriffe innerhalb einer Abfrage sehen denselben, unveränderlichen Datenstand - selbst wenn parallel andere Transaktionen Änderungen vornehmen.

Diese Sperrfreiheit ermöglicht höchste Performance bei reinen Leseoperationen und verhindert, dass Abfragen andere Datenbankoperationen blockieren.

#### 11.2.2 Normale Transaktionen bei Mutationen

`mutation`-Operationen arbeiten mit regulären Datenbanktransaktionen. Jeder Zugriff auf eine Tabelle setzt automatisch die entsprechenden Sperren:

- **Lesezugriffe** setzen Lesesperren (Shared Locks) auf Tabellenebene
- **Schreibzugriffe** setzen Schreibsperren (Exclusive Locks) auf Tabellenebene

Diese Sperren bleiben bis zum Transaktionsende bestehen. Das gewährleistet die Isolation der Transaktion, kann aber zu Konflikten mit anderen parallelen Transaktionen führen. Die detaillierte Behandlung von Sperren und deren Optimierung erfolgt in Kapitel 15.

#### 11.2.3 Atomizität als Grundprinzip

Eine Mutation wird nur dann festgeschrieben, wenn sie vollständig und fehlerfrei durchläuft. Jeder Laufzeitfehler führt zum sofortigen Abbruch und automatischen Zurückrollen aller bereits durchgeführten Änderungen. Dieses "Alles-oder-Nichts"-Prinzip gewährleistet, dass niemals inkonsistente Zwischenzustände in der Datenbank entstehen können.

### 11.3 Unterschiede in der Fehlerbehandlung

Die Fehlerbehandlung unterscheidet sich fundamental zwischen Abfragen und Mutationen:

**Bei Abfragen** führen Laufzeitfehler lediglich dazu, dass das betroffene Feld `null` zurückgibt. Die Ausführung der übrigen Felder wird fortgesetzt. Dies ermöglicht partielle Ergebnisse auch bei einzelnen fehlerhaften Feldern.

**Bei Mutationen** werden Laufzeitfehler standardmäßig nach außen propagiert. Sie führen zum Abbruch der gesamten Operation und zum Zurückrollen der Transaktion. Dies stellt sicher, dass eine Mutation entweder vollständig erfolgreich ist oder gar keine Änderungen hinterlässt.

### 11.4 Erweiterte Funktionalität in Mutationen

#### 11.4.1 Schema-Erweiterungen

In `mutation`-Operationen stehen erweiterte Objekttypen zur Verfügung. Eine Tabelle, die in Abfragen den Typ `TableNameTableQueryRead` zurückgibt, liefert in Mutationen `TableNameTableMutationRead`. Diese erweiterten Typen enthalten alle Felder des `TableQueryRead`-Objektes plus zusätzliche Schreiboperationen.

#### 11.4.2 Verfügbare Schreiboperationen

Zusätzlich zu den aus Abfragen bekannten Leseoperationen stehen in Mutationen folgende Schreiboperationen zur Verfügung:

| Operation | Zweck | Rückgabetyp |
|-----------|-------|-------------|
| `rowNew` | Neuen Datensatz erstellen | `RowMutationNew` |
| `rowCopy` | Datensatz kopieren | `RowMutationCopy` |
| `rowModify` | Datensatz ändern | `RowMutationModify` |
| `rowDelete` | Datensatz löschen | `RowMutationDelete` |
| `rowsCopy` | Mehrere Datensätze kopieren | `[RowMutationCopy]` |
| `rowsModify` | Mehrere Datensätze ändern | `[RowMutationModify]` |
| `rowsDelete` | Mehrere Datensätze löschen | `[RowMutationDelete]` |

#### 11.4.3 Row-Kontexte in Mutationen

Die verschiedenen `RowMutation`-Typen repräsentieren unterschiedliche Row-Kontexte eines Datensatzes:

- **`RowMutationNew`**: Ein neu angelegter Datensatz vor dem ersten Speichern
- **`RowMutationCopy`**: Eine Kopie eines bestehenden Datensatzes vor dem Speichern
- **`RowMutationModify`**: Ein zur Bearbeitung geöffneter bestehender Datensatz
- **`RowMutationDelete`**: Ein zur Löschung markierter Datensatz (nur Lesezugriff)
- **`RowMutationRead`**: Ein Datensatz im erweiterten Lesemodus mit Zugriff auf Mutation-spezifische Funktionen

Von diesen fünf Row-Kontexten sind nur drei schreibende Row-Kontexte: `RowMutationNew`, `RowMutationCopy` und `RowMutationModify`. In diesen Kontexten können Feldwerte gesetzt werden.

#### 11.4.4 Verfügbarkeit von Tabellen- und Datensatzfunktionen (`fn...`)

Die in Teil 1, Abschnitt 6.7 beschriebenen `fn...`-Funktionen zeigen in Mutationen ein funktionsspezifisches Verfügbarkeitsverhalten. Die Verfügbarkeit hängt primär davon ab, ob eine Funktion nur liest oder auch schreibt:

**Grundlegende Verfügbarkeitsregeln:**
- **Schreibende Funktionen**: Ausschließlich in `mutation`-Operationen verfügbar
  - Bei Instanzmethoden zusätzlich nur in schreibenden Row-Kontexten (`RowMutationNew`, `RowMutationCopy`, `RowMutationModify`)
- **Nur-lesende Funktionen**: Können sowohl in `query` als auch in `mutation` verfügbar sein
  - Die konkrete Verfügbarkeit ist funktionsspezifisch

!!! warning "Beachten Sie"

    Die tatsächliche Verfügbarkeit jeder einzelnen Funktion ist individuell definiert und kann von diesen generellen Mustern abweichen. Die konkrete Verfügbarkeit ist dem Schema der jeweiligen Tabelle zu entnehmen.

**Beispiel für tblAddresses:**
```graphql
mutation ($AdrNr: Any = null) {
  tblAddresses {
    rowNew {
      fldStatus(set: { string: "Lieferant" })
      # fnIsCustomer, fnIsVendor, fnIsIndividual bei tblAddresses
      # im RowMutationNew-Kontext nicht verfügbar
      
      rowSave {
        fldAdrNr @store(in: $AdrNr)
        # Nach dem Speichern im RowMutationRead-Kontext:
        fnIsCustomer  # Jetzt verfügbar
        fnIsVendor    # Jetzt verfügbar
        fnIsIndividual # Jetzt verfügbar
      }
    }
    
    # Klassenmethoden auf Tabellenebene bei tblAddresses verfügbar:
    fnIsCustomer(adrNr: $AdrNr)
    fnIsVendor(adrNr: $AdrNr)
    fnIsIndividual(adrNr: $AdrNr)
  }
}
```

### 11.5 Praktische Implikationen

#### 11.5.1 Wann Abfragen, wann Mutationen?

Die Wahl zwischen Abfrage und Mutation sollte sich nach dem beabsichtigten Zweck richten:

- **Abfragen** für alle reinen Leseoperationen, bei denen keine Datenänderungen erfolgen
- **Mutationen** für alle Operationen, die Daten erstellen, ändern oder löschen

Auch wenn technisch eine Mutation für reine Leseoperationen verwendet werden könnte, ist dies nicht empfehlenswert, da unnötige Sperren die Performance aller mit dem Datenserver verbundenen Anwendungen und Benutzer beeinträchtigen können. Dies betrifft interaktive Benutzer der microtech Software, andere GraphQL-Anfragen, COM-Clients, Automatisierungsdienste und alle weiteren Anwendungen, die auf denselben Datenserver zugreifen.

#### 11.5.2 Transaktionsplanung

Bei der Entwicklung von Mutationen sollte die transaktionale Natur berücksichtigt werden. Alle logisch zusammengehörigen Änderungen sollten in einer einzigen Mutation erfolgen, um Konsistenz zu gewährleisten. Gleichzeitig sollten Mutationen so kurz wie möglich gehalten werden, um Sperrenkonflikte zu minimieren.

## 12. Datensatzoperationen

Dieses Kapitel behandelt die fundamentalen Operationen zum Erstellen, Kopieren, Ändern und Löschen von Datensätzen. Diese Operationen bilden das Kernstück der Mutation-Funktionalität und sind essentiell für jede Datenmanipulation.

### 12.1 Das Row-Zustandsmodell

Jede Datensatzoperation gibt ein `Row`-Objekt eines zustandspezifischen Typs zurück, das die verfügbaren Operationen definiert:

- **Schreibende Row-Objekte** (`RowMutationNew`, `RowMutationCopy`, `RowMutationModify`): Erlauben das Setzen von Feldwerten
- **Nur-Lese-Row-Objekte** (`RowMutationDelete`, `RowMutationRead`): Erlauben nur Lesezugriff auf Felder

!!! warning "Beachten Sie"

    **Wichtige Unterscheidung:**

    Der Typ des `Row`-Objekts und der tatsächliche Zustand des dahinterliegenden Datensatzes können divergieren. Beispiel: Nach einem `rowSave` innerhalb eines `RowMutationModify`-Objekts ist der Datensatz bereits gespeichert und kann nur noch gelesen werden (Zustand: `Read`), aber das `Row`-Objekt bleibt vom Typ `RowMutationModify`. Weitere Schreibversuche im ursprünglichen Kontext führen dann zu Laufzeitfehlern, da der Datensatz-Zustand nicht mehr zum Kontext passt (siehe Kapitel 12.6 für Details).

### 12.2 Erstellen neuer Datensätze mit `rowNew`

Die `rowNew`-Operation erstellt einen neuen Datensatz und versetzt ihn in den `RowMutationNew`-Kontext.

#### 12.2.1 Grundlegende Verwendung

```graphql
mutation {
  tblProducts {
    rowNew {
      # Datenfelder setzen
      fldArtNr(set: { string: "PROD-NEW-001" } )
      fldBez1(set: { text: "Neuer Artikel" } as: DISPLAY_TEXT )
      fldVk0_Preis(set: { float: 99.95 } )
    } # Werte werden am Ende automatisch gespeichert
  }
}
```

Beim Erstellen neuer Datensätze können automatisch Feldwerte zugewiesen werden. Diese automatischen Zuweisungen erfolgen vor der Ausführung der expliziten Feldzugriffe und sind tabellenspezifisch.

#### 12.2.2 Bedingte Erstellung mit `ifNotExists`

Der optionale `ifNotExists`-Parameter verhindert die Erstellung eines Datensatzes, wenn bereits ein Datensatz mit den angegebenen Kriterien existiert:

```graphql
mutation {
  tblProducts {
    rowNew(
      ifNotExists: { 
        exactMatch: { byNr: { kf1ArtNr: { string: "PROD-NEW-001" } } }
      }
    ) {
      # Datenfelder setzen
      fldArtNr(set: { string: "PROD-NEW-001" } )
      fldBez1(set: { text: "Neuer Artikel" } as: DISPLAY_TEXT )
      fldVk0_Preis(set: { float: 99.95 } )
    } # Neuer Datensatz wird am Ende automatisch gespeichert
  }
}
```

Bei bereits vorhandenem Datensatz gibt `rowNew` in diesem Fall `null` zurück, anstatt einen Fehler auszulösen.

### 12.3 Kopieren bestehender Datensätze mit `rowCopy`

Die `rowCopy`-Operation erstellt eine Kopie eines existierenden Datensatzes und versetzt die Kopie in den `RowMutationCopy`-Kontext.

```graphql
mutation {
  tblProducts {
    rowCopy(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-NEW-001" } } }
    ) {
      # Neue Artikelnummer für die Kopie vergeben
      fldArtNr(set: { string: "PROD-NEW-001-COPY" })
      
      # Andere Felder können beibehalten oder geändert werden
      fldBez1(set: { text: "Kopie von Original" } as: DISPLAY_TEXT )
      
      # Nicht explizit gesetzte Felder werden vom Original übernommen
      fldVk0_Preis # Behält den Wert des Originals 
    } # Neuer (kopierter) Datensatz wird am Ende automatisch gespeichert
  }
}
```

Welche Felder beim Kopieren übernommen werden und welche neu gesetzt werden müssen (wie eindeutige Schlüssel), ist tabellenspezifisch. Wird der Quelldatensatz nicht gefunden, gibt `rowCopy` `null` zurück.

Wie bei `rowRead` kann auch bei `rowCopy` der optionale `modifyLSN`-Parameter verwendet werden (siehe Teil 1, Kapitel 3.5), um sicherzustellen, dass der zu kopierende Datensatz eine bestimmte Version hat.

### 12.4 Ändern bestehender Datensätze mit `rowModify`

Die `rowModify`-Operation öffnet einen bestehenden Datensatz zur Bearbeitung im `RowMutationModify`-Kontext:

```graphql
mutation {
  tblProducts {
    rowModify(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-NEW-001" } } }
    ) {
      # Felder ändern
      fldHistKz(set: { boolean: true } )  
      fldVk0_Rab0_Sz(set: { float: 15.0 } as: DISPLAY_TEXT )
      
      # Unveränderte Felder können weiterhin gelesen werden
      fldBez1(as: DISPLAY_TEXT )
      fldVk0_Preis # Behält den Wert des Originals 
    } # Änderungen am Datensatz werden am Ende automatisch gespeichert
  }
}
```

Der zu ändernde Datensatz wird über dieselben Parameter identifiziert wie bei Leseoperationen. Nicht gefundene Datensätze führen zu einem `null`-Rückgabewert.

Der optionale `modifyLSN`-Parameter (siehe Teil 1, Kapitel 3.5) kann auch bei `rowModify` verwendet werden, um Optimistic Locking zu implementieren. Ein praktisches Beispiel dazu findet sich in Abschnitt 12.6.5.

### 12.5 Löschen von Datensätzen mit `rowDelete`

Die `rowDelete`-Operation markiert einen Datensatz zur Löschung. Der Datensatz befindet sich im `RowMutationDelete`-Kontext, der nur Lesezugriffe erlaubt:

```graphql
mutation {
  tblProducts {
    copy: rowDelete(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-NEW-001-COPY" } } }
    ) {
      # Vor der Löschung können Felder noch gelesen werden
      fldArtNr
      fldBez1(as: DISPLAY_TEXT)     
      # Schreibzugriffe sind nicht möglich - dies ist kein schreibender Row-Kontext
    } # Nach diesem Block ist der Datensatz gelöscht

    new: rowDelete(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-NEW-001" } } }
    ) {
      # GraphQL erfordert, dass mindestens ein Feld zurückgegeben wird
      fldArtNr
    } # Nach diesem Block ist der Datensatz gelöscht
  }
}
```

Die eigentliche Löschung erfolgt am Ende des `rowDelete`-Blocks. Das Verhalten bei abhängigen Datensätzen ist tabellenspezifisch. Je nach interner Konfiguration werden abhängige Datensätze automatisch mit gelöscht, ihre Referenzen auf null gesetzt oder die Löschung wird verhindert.

Wie bei allen Operationen mit `exactMatch` unterstützt auch `rowDelete` den optionalen `modifyLSN`-Parameter (siehe Teil 1, Kapitel 3.5), um sicherzustellen, dass nur eine bestimmte Version des Datensatzes gelöscht wird.

### 12.5.1 Der `ignoreWarnings`-Parameter

Die GraphQL-Schnittstelle verhält sich analog zur existierenden microtech ERP-Benutzeroberfläche. In der UI werden beim Löschen von Datensätzen in verschiedenen Fällen Warnungen angezeigt, die vom Benutzer bestätigt werden müssen, bevor der Datensatz tatsächlich gelöscht wird. Beispiele für solche Warnungen sind:

- Hinweise auf abhängige Datensätze
- Warnungen vor Auswirkungen auf andere Geschäftsprozesse
- Informationen über verknüpfte Dokumente

Da diese interaktive Bestätigung über GraphQL nicht möglich ist, wird bei auftretenden Warnungen standardmäßig ein Fehler ausgelöst und die Löschoperation abgebrochen. Um Datensätze trotz Warnungen zu löschen, muss der optionale `ignoreWarnings`-Parameter explizit auf `true` gesetzt werden:

```graphql
mutation {
  tblProducts {
    # Löschen ohne ignoreWarnings - wird bei Warnungen abgebrochen
    deleteWithCheck: rowDelete(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }
    ) {
      fldArtNr
    }

    # Löschen mit ignoreWarnings - Warnungen werden ignoriert
    deleteForced: rowDelete(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-002" } } }
      ignoreWarnings: true
    ) {
      fldArtNr
    }
  }
}
```

!!! warning "Beachten Sie"

    - Der Standardwert für `ignoreWarnings` ist `false`
    - Dies stellt sicher, dass wichtige Warnungen nicht unbeabsichtigt übergangen werden
    - Der Parameter sollte nur gesetzt werden, wenn die möglichen Konsequenzen bekannt und akzeptiert sind
    - Kritische Fehler (im Gegensatz zu Warnungen) können auch mit `ignoreWarnings: true` nicht übergangen werden
    
    Wie bei allen Operationen mit `exactMatch` unterstützt auch `rowDelete` den optionalen `modifyLSN`-Parameter (siehe Teil 1, Kapitel 3.5), um sicherzustellen, dass nur eine bestimmte Version des Datensatzes gelöscht wird.

### 12.6 Speicheroperationen

#### 12.6.1 Implizite und explizite Speicherung

Standardmäßig erfolgt die Speicherung implizit am Ende eines Row-Blocks. Für spezielle Anforderungen stehen explizite Speicheroperationen zur Verfügung:

- **`rowSave`**: Speichert den Datensatz und wechselt in den `RowMutationRead`-Kontext
- **`rowSaveAndModify`**: Speichert den Datensatz und wechselt/verbleibt im `RowMutationModify`-Kontext

#### 12.6.2 Zugriff auf generierte Werte

Der Hauptgrund für explizite Speicheroperationen ist der Zugriff auf automatisch generierte Werte:

```graphql
mutation {
  tblProducts {
    rowNew {
      # Datenfelder setzen
      fldArtNr(set: { string: "PROD-NEW-001" } )
      fldBez1(set: { text: "Neuer Artikel" } as: DISPLAY_TEXT )
      fldVk0_Preis(set: { float: 99.95 } )
      # Explizit speichern
      rowSave {
        # Jetzt ist der Datensatz schon gespeichert und generierte Werte sind verfügbar
        fldModifyLSN  # Versionsnummer
        fldID         # Automatisch generierte ID
      } 
    } # Da der Datensatz schon gespeichert ist, passiert hier nichts mehr
  }
}
```

#### 12.6.3 Zustandsübergänge bei Speicheroperationen

Die Speicheroperationen führen zu definierten Zustandsübergängen:

**Nach `rowSave`:**
- Aus allen schreibenden Row-Kontexten → `RowMutationRead`
- Keine weiteren Schreibzugriffe möglich
- Zugriff auf verlinkte Tabellen möglich

**Nach `rowSaveAndModify`:**
- Aus `RowMutationNew` oder `RowMutationCopy` → `RowMutationModify`
- Aus `RowMutationModify` → verbleibt in `RowMutationModify`
- Weitere Schreibzugriffe möglich
- Kein Zugriff auf verlinkte Tabellen (`lnk...`)

#### 12.6.4 Wichtige Einschränkung

Nach dem Aufruf von `rowSave` oder `rowSaveAndModify` führen weitere Schreiboperationen im ursprünglichen Row-Kontext zu einem Laufzeitfehler:

```graphql
mutation {
  tblProducts {
    rowNew {
      # Datenfelder setzen
      fldArtNr(set: { string: "PROD-003" } )
      # Explizit speichern und weiter modifizieren
      rowSaveAndModify {
        # Jetzt ist der Datensatz gespeichert, aber weiterhin schreibar
        fldBez1(set: { text: "Neuer Artikel" } as: DISPLAY_TEXT )
        fldModifyLSN 
      } # Änderungen am Datensatz werden am Ende automatisch gespeichert
      
      # lesen erlaubt
      fldModifyLSN
      
      # SCHREIBEN VERBOTEN: Weitere Schreiboperationen hier schlagen fehl
      fldSuchBeg(set: { text: "Test" })  # Laufzeitfehler           
    }
  }
} # Laufzeitfehler rollt alle Änderungen zurück
```

```json
{
  "data": {
    "tblProducts": {
      "rowNew": {
        "fldArtNr": "PROD-003",
        "rowSaveAndModify": {
          "fldBez1": "Neuer Artikel",
          "fldModifyLSN": "9161168561880169771"
        },
        "fldModifyLSN": "9161168561880169774",
        "fldSuchBeg": null
      }
    }
  },
  "errors": [
    {
      "message": "Can't set field on a row that is not in New, Copy, or Modify state",
      "locations": [
        {
          "line": 17,
          "column": 7
        }
      ],
...
```


#### 12.6.5 Optimistic Locking mit modifyLSN

Der `modifyLSN`-Parameter (siehe Teil 1, Kapitel [3.5 Konsistenzsicherung mit `modifyLSN`-Parameter](./graphqldoku.md#35-konsistenzsicherung-mit-modifylsn-parameter)) kann auch bei `rowModify` verwendet werden, um Optimistic Locking zu implementieren. Dies ermöglicht die Erkennung von Konflikten bei gleichzeitigen Änderungen:

!!! warning "Beachten Sie"

    Grundsätzlich kann sich die fldModifyLSN auch ohne bewusste Benutzeränderung ändern (z. B. direkt nach der Anlage oder Änderung eines Datensatzes). Eine abweichende modifyLSN bedeutet lediglich, dass der Datensatz geändert wurde, ohne zu wissen was geändert wurde oder durch welche Aktion. Weitere Informationen hierzu siehe microtech GraphQL Handbuch Teil 1 - [3.5.1 Grundlagen der Log Sequence Numbers (LSN)](./graphqldoku.md#351-grundlagen-der-log-sequence-numbers-lsn) 

```graphql
mutation ModifyAddressMutationOptimisticLocking (
  $adrNr: String = "10000"
  $modifyLSN: BigInt = 9161168561880151768
  $notFound: Boolean! = false
) {
  tblAddresses {
    modified:rowModify (
      kf1AdrNr: { text: $adrNr }
      modifyLSN: $modifyLSN
    ) 
      @onNull(
        returnValue: skip
        set:{ var: $notFound }
      ) 
    {     
      fldID
      fldAdrNr
      fldInsertLSN
      fldModifyLSN
      fldInfo( set: {text: "Modified"} as: TEXT )
      rowSave {
        fldModifyLSN
      }
    }
       
    mismatchedLSN:rowRead (      
      kf1AdrNr:{ text: $adrNr }
    ) 
      @include( if: $notFound ) 
    {
      fldID
      fldAdrNr
      fldInsertLSN
      fldModifyLSN
      fldInfo(as:TEXT)
    }
  }
}
```

```json
{
  "data": {
    "tblAddresses": {
      "mismatchedLSN": {
        "fldID": 1501,
        "fldAdrNr": "10000",
        "fldInsertLSN": "9161168561880064521",
        "fldModifyLSN": "9161168561880151777",
        "fldInfo": "Unmodified"
      }
    }
  }
}
```

```graphql
mutation ModifyAddressMutationOptimisticLocking (
  $adrNr: String = "10000"
  $modifyLSN: BigInt = 9161168561880151777
  $notFound: Boolean! = false
) {
  ...
}
```

```json
{
  "data": {
    "tblAddresses": {
      "modified": {
        "fldID": 1501,
        "fldAdrNr": "10000",
        "fldInsertLSN": "9161168561880064521",
        "fldModifyLSN": "9161168561880151777",
        "fldInfo": "Modified",
        "rowSave": {
          "fldModifyLSN": "9161168561880169332"
        }
      }
    }
  }
}
```
Dieses Muster ermöglicht es:
- Zu erkennen, ob ein Datensatz zwischen Lesen und Schreiben geändert wurde
- Bei Konflikten die aktuelle Version zu lesen
- Dem Client die Möglichkeit zu geben, auf Konflikte zu reagieren

### 12.7 Massenoperationen mit Listen-Feldern

Die Listen-Varianten (`rowsCopy`, `rowsModify`, `rowsDelete`) führen die entsprechende Operation für jeden Datensatz der Ergebnismenge aus:

```graphql
mutation {
  tblProducts {
    rowsModify(
      allBetween: {
        byNr: {
          kf1ArtNr: {
            from: { string: "PROD-001" },
            to: { string: "PROD-199" }
          }
        }
      }
    ) {     
      # Wert vor Änderungen auslesen
      before: fldHistKz
      
      # Änderungen werden auf alle gefundenen Artikel angewendet
      fldHistKz(set: { boolean: true })
      
      # Individuelle Werte jedes Datensatzes können abgefragt werden
      fldArtNr
    }
  }
}
```

Ein Fehler bei der Bearbeitung eines einzelnen Datensatzes führt zum Abbruch der gesamten Operation. Alle Änderungen werden zurückgerollt - eine Teilausführung ist nicht möglich.

## 13. Feldmanipulation und Parameter

Nach den grundlegenden Datensatzoperationen behandelt dieses Kapitel die detaillierte Manipulation von Feldwerten und die verschiedenen Parameter-Typen, die dabei zur Verfügung stehen.

### 13.1 Feldmanipulation mit dem `set`-Parameter

In schreibenden Row-Kontexten (`RowMutationNew`, `RowMutationCopy`, `RowMutationModify`) können Feldwerte über den `set`-Parameter gesetzt werden.

#### 13.1.1 Typspezifische Eingabe

Jeder Feldtyp hat typenspezifische Eingabemöglichkeiten:

```graphql
mutation {
  tblProducts {
    rowNew {
      # String-Felder
      fldArtNrString: fldArtNr(set: { string: "PROD-005" })               # Native Eingabe
      fldArtNrText:   fldArtNr(set: { text: "PROD-005" })                 # Text-Eingabe
      
      # Numerische Felder
      fldVk0_PreisFloat: fldVk0_Preis(set: { float: 99.95 })              # Native Eingabe
      fldVk0_PreisText:  fldVk0_Preis(set: { text: "99,95" })             # Lokalisierte Text-Eingabe
      
      # Datumsfelder      
      fldGspAbDatLocalDate: fldGspAbDat(set: { localdate: "2023-12-24" }) # ISO-Format
      fldGspAbDatText:      fldGspAbDat(set: { text: "24.12.2023" })      # Lokalisiertes Format
      
      # NULL-Werte
      fldGspAbDatNULL: fldGspAbDat(set: {})                               # Setzt Feld auf NULL
    }
  }
}
```

Die verfügbaren Möglichkeiten zur Werteangabe sind dem Schema zu entnehmen.

#### 13.1.2 Rückgabewerte nach dem Setzen

Der `as`-Parameter bestimmt das Format des zurückgegebenen Wertes nach dem Setzen:

```graphql
mutation {
  tblProducts {
    rowModify(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }) {
      # Wert setzen und formatiert zurückgeben
      Text:  fldVk0_Preis(set: { float: 129.95 }, as: TEXT)  # Gibt "129,95" zurück
      Float: fldVk0_Preis(set: { float: 129.95 }, as: FLOAT) # Gibt 129.95 zurück
    }
  }
}
```

#### 13.1.3 Verfügbarkeit von `set`-Parametern

Nicht alle Felder sind in allen schreibenden Row-Kontexten schreibbar. Die Verfügbarkeit ist:
- Tabellenspezifisch
- Kontextabhängig (kann sich zwischen `New`, `Copy` und `Modify` unterscheiden)
- Im Schema durch das Vorhandensein des `set`-Parameters erkennbar

#### 13.1.4 Zusätzliche Parameter

Neben dem Wert selbst können beim `set`-Parameter zusätzliche Optionen angegeben werden, die das Verhalten beim Setzen des Feldwertes beeinflussen:

##### allowAutoNrOnSave

Der optionale Parameter `allowAutoNrOnSave` steht nur bei bestimmten Datenfeldern zur Verfügung, typischerweise bei primären Schlüsselfeldern bestimmter Tabellen.

Wenn vorhanden und `true`, gibt dieser Parameter an, dass der gesetzte Feldwert als Startwert angesehen werden soll. Beim Speichern des Datensatzes (egal ob explizit mit `rowSave` oder implizit) wird, falls der Wert bereits vorhanden ist, automatisch hochgezählt bis ein freier Wert gefunden wird.

```graphql
mutation {
  tblAddresses {            
    rowNewCustomer: rowNew {
      fldAdrNr(set: {
        text: "10000" 
        allowAutoNrOnSave: true
      })

      rowSave {
        fldAdrNr
      }
    }
  }
}
```

```json
{
  "data": {
    "tblAddresses": {
      "rowNewCustomer": {
        "fldAdrNr": "10000",
        "rowSave": {
          "fldAdrNr": "10020"
        }
      }
    }
  }
}
```

In diesem Beispiel wurde der Wert "10000" als Startwert angegeben. Da bereits Adressen mit den Nummern 10000 bis 10019 existierten, wurde automatisch die nächste freie Nummer "10020" vergeben.

##### suppressRelatedUpdates

Der optionale Parameter `suppressRelatedUpdates` ist nur für Datenfelder vorhanden, bei denen das Setzen zu Änderungen in anderen Feldern führen kann.

Zum Beispiel: Wenn `fldZahlBed` (Zahlungsbedingung) auf einen Wert gesetzt wird, der einer der Vorgaben aus den Parametern entspricht, dann werden - wie auch im Eingabeformular im microtech ERP Client - davon abhängige Felder automatisch mit gesetzt (Skontosätze, Skontotage, etc.).

Wenn der Parameter angegeben und `true` ist, wird das automatische Setzen dieser abhängigen Felder unterdrückt.

```graphql
mutation {
  tblAddresses {            
    rowNewCustomer: rowModify (
      kf1AdrNr: {
        text: "10000"       
      }
    ) {
      A: _if (value: true) {
        fldZahlBed(set: {text: "8 Tage 2 %, 30 Tage Netto"})
        fldSktoSz1       
        fldSktoSz2
        fldSktoTg1
        fldSktoTg2
      }

      B: _if (value: true) {
        fldZahlBed(set: {text: "10 Tage 3 %, 30 Tage Netto"})
        fldSktoSz1       
        fldSktoSz2
        fldSktoTg1
        fldSktoTg2
      }

      C: _if (value: true) {
        fldZahlBed(set: {
          text: "8 Tage 2 %, 30 Tage Netto"
          suppressRelatedUpdates: true                
        })
        fldSktoSz1       
        fldSktoSz2
        fldSktoTg1
        fldSktoTg2
      }
    }
  }
}
```

```json
{
  "data": {
    "tblAddresses": {
      "rowNewCustomer": {
        "A": {
          "fldZahlBed": "8 Tage 2 %, 30 Tage Netto",
          "fldSktoSz1": 2,
          "fldSktoSz2": null,
          "fldSktoTg1": 8,
          "fldSktoTg2": 0
        },
        "B": {
          "fldZahlBed": "10 Tage 3 %, 30 Tage Netto",
          "fldSktoSz1": 3,
          "fldSktoSz2": null,
          "fldSktoTg1": 10,
          "fldSktoTg2": null
        },
        "C": {
          "fldZahlBed": "8 Tage 2 %, 30 Tage Netto",
          "fldSktoSz1": 3,
          "fldSktoSz2": null,
          "fldSktoTg1": 10,
          "fldSktoTg2": null
        }
      }
    }
  }
}
```

Im Vergleich zwischen A und C ist zu sehen:
- Bei A wurden die Skonto-Felder automatisch entsprechend der Zahlungsbedingung "8 Tage 2 %, 30 Tage Netto" gesetzt
- Bei C wurden durch `suppressRelatedUpdates: true` die automatischen Änderungen unterdrückt, sodass die Skonto-Felder ihre vorherigen Werte behalten haben (die im Beispiel B gesetzt wurden)

### 13.2 Row-Assign-Parameter

Manche Tabellen bieten `row...`-`assign...`-Parameter, die eine automatische Befüllung mehrerer Felder basierend auf Daten aus anderen Datensätzen ermöglichen.

#### 13.2.1 Verwendung bei der Neuanlage

```graphql
mutation {
  tblContacts {
    rowNew(assignAddress: { kf1AdrNr: { text: "10000" } }) {
      # Felder wurden automatisch aus der Adresse befüllt
      fldNr        # "10000" - übernommen
      fldAnsInfo   # Weitere Informationen aus der Adresse
      
      # Zusätzliche Felder können manuell gesetzt werden
      fldInfo(set: { text: "Neuer Kontakt" })
    }
  }
}
```

#### 13.2.2 Verfügbarkeit und Syntax

`row...`-`assign...`-Parameter:
- Sind bei `rowNew`, `rowCopy` und `rowModify` verfügbar
- Verwenden dieselbe Identifikationssyntax wie `rowRead`
- Können verkürzt ohne `exactMatch` geschrieben werden

#### 13.2.3 Mehrere Assign-Parameter

Wenn eine Tabelle mehrere `row...`-`assign...`-Parameter unterstützt, werden diese in der durch das Schema definierten Reihenfolge ausgeführt, nicht in der Reihenfolge ihrer Angabe:

```graphql
mutation {
  tblContacts {
    rowNew(
      assignUser: { exactMatch: { byAnmNa: { kf1AnmNa: { text: "Supervisor" } } } }, # Reihenfolge der Angabe            
      assignAddress: { kf1AdrNr: { text: "10000" } }                                 # ist nicht relevant
    ) {
      # Die Ausführung erfolgt in Schema-definierter Reihenfolge:
      # assignAddress, assignBank, assignUser, assignSalesRepresentative
    }
  }
}
```

#### 13.2.4 Technische Aspekte

`row...`-`assign...`-Parameter führen intern einen lesenden Zugriff auf die referenzierte Tabelle aus. Dies hat folgende Auswirkungen:

- Eine Lesesperre wird auf die Zieltabelle gesetzt (falls noch nicht vorhanden)
- Der gefundene Datensatz wird gelesen und die relevanten Werte extrahiert
- Diese Werte werden dann auf den aktuellen Datensatz übertragen

Bei der Verwendung von `@acquireLocks` sollten Tabellen, die über `row...`-`assign...`-Parameter gelesen werden, im `forReading`-Parameter berücksichtigt werden.

### 13.3 Feld-Assign-Parameter

Zusätzlich zu `row...`-`assign...`-Parametern bieten manche Datenfelder eigene `fld...`-`assign...`-Parameter als Alternative zum `set`-Parameter.

#### 13.3.1 Konzept und Verwendung

`fld...`-`assign...`-Parameter ermöglichen die automatische Wertzuweisung basierend auf der Suche in einer verknüpften Tabelle:

```graphql
mutation {
  tblProducts {
    rowModify(
      exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }
    ) {
      # Statt den genauen Wert zu kennen:
      # fldWgrNr(set: { string: "WGR-123" })
      
      # Automatische Zuweisung basierend auf Suche:
      fldWgrNr(assignProductGroup: { 
        exactMatch: { byBez: { kf1Bez: { text: "Elektronik" } } }
      })
    }
  }
}
```

#### 13.3.2 Eigenschaften

- **Exklusivität**: Bei einem Feld kann entweder `set` oder ein `fld...`-`assign...`-Parameter verwendet werden, niemals beide gleichzeitig
- **Verfügbarkeit**: Typischerweise bei Feldern, die auf andere Datensätze verweisen (Fremdschlüssel)
- **Syntax**: Identisch mit der Syntax von `rowRead` für die Datensatzsuche
- **Lesezugriff**: Wie bei `row...`-`assign...`-Parametern wird eine Lesesperre auf die referenzierte Tabelle gesetzt

#### 13.3.3 Praktischer Nutzen

`fld...`-`assign...`-Parameter sind besonders nützlich wenn:
- Der exakte Wert eines Referenzfeldes nicht bekannt ist
- Die Zuweisung basierend auf Geschäftslogik erfolgen soll
- Konsistenz durch automatische Verknüpfung sichergestellt werden soll

### 13.4 Automatische Feldwertzuweisungen

Neben den expliziten Parametern können automatische Feldwertzuweisungen in verschiedenen Situationen erfolgen:

#### 13.4.1 Zeitpunkte automatischer Zuweisungen

Automatische Zuweisungen können erfolgen:
- Bei `rowNew` oder `rowCopy` vor der Ausführung der Feldzugriffe
- Bei Verwendung von Row-Assign-Parametern
- Beim Setzen eines Feldwertes (Auswirkungen auf andere Felder)
- Beim Speichern eines Datensatzes

#### 13.4.2 Charakteristika

- Die genauen Regeln sind tabellenspezifisch
- Können Standardwerte, berechnete Werte oder abhängige Aktualisierungen sein
- Sind nicht immer aus dem Schema ersichtlich
- Können durch explizite Wertzuweisungen überschrieben werden, sofern die automatische Zuweisung vor der expliziten Zuweisung erfolgt

#### 13.4.3 Beispielhafte Szenarien

Typische automatische Zuweisungen (tabellenspezifisch):
- Generierung von IDs oder laufenden Nummern
- Setzen von Zeitstempeln (Erstellungs-/Änderungsdatum)
- Berechnung abhängiger Felder (z.B. Gesamtpreise)
- Übernahme von Standardwerten aus Stammdaten

### 13.5 Reihenfolge von Feldzugriffen

Die Reihenfolge von Feldzugriffen kann relevant sein, da Felder aufeinander Bezug nehmen können:

```graphql
mutation {
  tblProducts {
    rowNew {
      fldArtNr(set: { text: "PROD-001" } )
      
      fldStSchl(set: {text: "2"} )
            
      fldVk0_IklStKz(set: {boolean: true} )
      fldVk0_Preis(set: {float: 100} )
      fldVk0_PreisNt
      fldVk0_PreisBt
      
      fldVk1_IklStKz(set: {boolean: false} )
      fldVk1_Preis(set: {float: 100} )
      fldVk1_PreisNt
      fldVk1_PreisBt   
    }
  }
}
```

Diese sequenzielle Verarbeitung ermöglicht es, dass:
- Automatische Berechnungen auf bereits gesetzte Werte zugreifen können
- Validierungen den aktuellen Zustand des Datensatzes berücksichtigen
- Abhängige Felder korrekt aktualisiert werden

### 13.6 Betragsgruppen in Mutationen (`aco...`)

Die in Teil 1, Abschnitt 6.5 beschriebenen Betragsgruppen (`aco...`) sind in schreibenden Row-Kontexten (`RowMutationNew`, `RowMutationCopy`, `RowMutationModify`) um Schreiboperationen erweitert. Dies ermöglicht die direkte Manipulation von Beträgen mit ihrer Steueraufschlüsselung.

#### 13.6.1 Verfügbarkeit

Betragsgruppen-Schreiboperationen sind **ausschließlich in schreibenden Row-Kontexten** verfügbar. In Lese-Kontexten (`RowMutationRead`, `RowQueryRead`) stehen nur die in Teil 1 beschriebenen Lesefelder zur Verfügung.

| Row-Kontext | Lesen | Schreiben |
|-------------|-------|----------|
| `RowMutationNew` | ✓ | ✓ |
| `RowMutationCopy` | ✓ | ✓ |
| `RowMutationModify` | ✓ | ✓ |
| `RowMutationRead` | ✓ | ✗ |
| `RowMutationDelete` | ✓ | ✗ |

#### 13.6.2 Schreiboperationen auf Gesamtebene

Auf der `AmountComposition`-Ebene stehen folgende Schreiboperationen zur Verfügung. Diese Felder sind keine Datenbankfelder (erkennbar am fehlenden `fld`-Präfix), daher unterscheidet sich ihre Syntax von der Feldmanipulation mit dem `set`-Parameter bei Datenbankfeldern.

**`clear(isGross: Boolean): Void`**

Setzt alle Beträge der Betragsgruppe auf Null zurück. Der optionale Parameter `isGross` bestimmt dabei, ob die Betragsgruppe nach dem Zurücksetzen als Brutto (`true`) oder Netto (`false`) gespeichert werden soll. Wird der Parameter nicht angegeben, wird der bisherige Speichermodus beibehalten. Der Rückgabewert ist immer `null`.

```graphql
mutation {
  tblTransactionItems {
    rowModify(exactMatch: { byNr: { kf1PosNr: { int: 1 } } }) {
      acoEPr {
        # Betragsgruppe zurücksetzen, Speicherung als Brutto
        clear(isGross: true)
      }
    }
  }
}
```

**`totalGrossAmount(set: Float): Float`**

Setzt den Gesamt-Bruttobetrag der Betragsgruppe und gibt den (neuen) Wert zurück. Ohne `set`-Parameter wird nur der aktuelle Wert gelesen.

**`totalNetAmount(set: Float): Float`**

Setzt den Gesamt-Nettobetrag der Betragsgruppe und gibt den (neuen) Wert zurück. Ohne `set`-Parameter wird nur der aktuelle Wert gelesen.

**`isCalculatedAndStoredAsGross(set: Boolean): Boolean`**

Legt fest, ob die Betragsgruppe als Brutto (`true`) oder Netto (`false`) gespeichert und berechnet wird. Dies beeinflusst, wo Rundungsdifferenzen auftreten können. Gibt den (neuen) Wert zurück.

```graphql
mutation {
  tblTransactionItems {
    rowModify(exactMatch: { byNr: { kf1PosNr: { int: 1 } } }) {
      acoEPr {
        # Speicherung auf Brutto umstellen
        isCalculatedAndStoredAsGross(set: true)

        # Bruttobetrag setzen
        totalGrossAmount(set: 119.00)
      }
    }
  }
}
```

**`isDebit(set: Boolean): Boolean` (nur bei Soll/Haben-Feldern)**

Für Betragsgruppen, die einen Soll/Haben-Wert darstellen (z. B. Salden), kann über dieses Feld festgelegt werden, ob es sich um einen Soll-Betrag (`true`) oder Haben-Betrag (`false`) handelt. Gibt den (neuen) Wert zurück.

#### 13.6.3 Schreiboperationen auf Einzelwertebene

Wenn das zugrundeliegende Datenfeld Einzelwerte pro Steuersatz unterstützt, können auch die einzelnen `AmountCompositionEntry`-Objekte innerhalb von `aces` oder über `aceByTaxCode` beschrieben werden:

**`grossAmount(set: Float): Float`**

Setzt den Bruttobetrag für den jeweiligen Steuersatz und gibt den (neuen) Wert zurück. Ohne `set`-Parameter wird nur der aktuelle Wert gelesen.

**`netAmount(set: Float): Float`**

Setzt den Nettobetrag für den jeweiligen Steuersatz und gibt den (neuen) Wert zurück. Ohne `set`-Parameter wird nur der aktuelle Wert gelesen.

```graphql
mutation {
  tblTransactionItems {
    rowModify(exactMatch: { byNr: { kf1PosNr: { int: 1 } } }) {
      acoEPr {
        # Einzelwert für Steuersatz 19% setzen (taxCode 3)
        # Der Eintrag wird ggf. neu angelegt, falls noch nicht vorhanden
        aceByTaxCode(taxCode: 3) {
          grossAmount(set: 119.00)
        }

        # Über die Liste können nur bereits vorhandene Einträge geändert werden
        aces {
          taxCode
          grossAmount(set: 0.00)  # Alle vorhandenen auf 0 setzen
        }
      }
    }
  }
}
```

#### 13.6.4 Kombination von Lesen und Schreiben

Innerhalb einer Betragsgruppen-Operation können Lese- und Schreibzugriffe kombiniert werden. Die Reihenfolge der Feldangabe bestimmt dabei die Ausführungsreihenfolge:

```graphql
mutation {
  tblTransactionItems {
    rowModify(exactMatch: { byNr: { kf1PosNr: { int: 1 } } }) {
      acoEPr {
        # Zuerst den aktuellen Wert lesen
        vorher: totalGrossAmount

        # Neuen Bruttowert setzen (gibt den gesetzten Wert zurück)
        totalGrossAmount(set: 150.00)

        # Automatisch berechnete Werte auslesen
        totalNetAmount
        totalTaxAmount
      }
    }
  }
}
```

#### 13.6.5 Wichtige Hinweise

- **Automatische Speicherung**: Änderungen an Betragsgruppen werden zusammen mit dem übergeordneten Datensatz gespeichert. Es gibt keine separate Speicheroperation für die Betragsgruppe selbst.
- **Internes Speicherformat**: Eine Betragsgruppe speichert intern immer entweder Brutto- oder Nettobeträge pro Steuerschlüssel (gesteuert durch `isCalculatedAndStoredAsGross`). Der jeweils andere Wert sowie der Steuerbetrag werden automatisch berechnet.
- **Verteilung bei Gesamtbeträgen**: Beim Setzen von `totalGrossAmount` oder `totalNetAmount` wird die Änderung relativ auf die vorhandenen Einzeleinträge verteilt. Sind noch keine Einträge vorhanden, wird ein neuer Eintrag mit dem Standard-Steuersatz erstellt.
- **Verfügbarkeit von Einzelwerten**: Obwohl die Aufschlüsselung nach Steuerschlüsseln intern immer vorhanden ist, ist die Bearbeitung auf Einzelwertebene (`aces`, `aceByTaxCode`) nicht bei allen Betragsgruppen-Datenfeldern freigeschaltet. Die Verfügbarkeit ist dem Schema zu entnehmen.

## 14. Beziehungen und komplexe Datenstrukturen

Nach den Grundlagen der Datensatz- und Feldoperationen widmet sich dieses Kapitel der Arbeit mit Beziehungen zwischen Datensätzen. Die drei aus Teil 1 bekannten Beziehungstypen zeigen in Mutationen spezifische Verhaltensweisen und erweiterte Möglichkeiten.

### 14.1 Verfügbarkeit von Beziehungsstrukturen

Die Verfügbarkeit und Funktionalität der verschiedenen Beziehungstypen hängt vom aktuellen Row-Kontext ab:

| Beziehungstyp | `RowMutationRead` | Schreibende Row-Kontexte (`New`/`Copy`/`Modify`) |
|---------------|-------------------|--------------------------------------------------|
| **Verlinkungen (`lnk...`)** | Vollständige Schreiboperationen | Nicht verfügbar |
| **Verweise (`row...`)** | Gibt `RowMutationRead`-Typen zurück | Gibt `RowQueryRead`-Typen zurück |
| **Verschachtelte Tabellen (`tbl...`)** | Nur Leseoperationen | Vollständige Schreiboperationen |

Diese Verfügbarkeitsregeln basieren auf technischen Notwendigkeiten zur Vermeidung von Sperrkonflikten und Gewährleistung der Datenkonsistenz.

### 14.2 Arbeiten mit Verlinkungen (`lnk...`)

#### 14.2.1 Schreiboperationen über Verlinkungen

In `RowMutationRead`-Kontexten bieten Verlinkungen erweiterte Möglichkeiten zur Manipulation verknüpfter Datensätze:

```graphql
mutation {
  tblAddresses {
    rowRead(exactMatch: { byNr: { kf1AdrNr: { string: "10000" } } }) {
      # Im RowMutationRead-Kontext
      lnkPostalAddresses {
        
        # Bestehende Anschriften auflisten
        rowsReadBefore: rowsRead(
          byAdrNrAnsNr: { usingAdrNr: {} } 
        ) {
          fldAnsNr
          fldNamen
          fldStdReKz
          fldStdLiKz
          fldStdInKz
          fldInfo(as: TEXT)
        }
        
        # Neue verknüpfte Anschrift erstellen
        rowNew(setAdrNrAnsNr: usingAdrNr) {
          fldNa2(set: { text: "Neue Lieferanschrift" })
          fldStdLiKz(set: { boolean: true })
          rowSave {
            fldAnsNr
          }
        }
        
        # Bestehende Anschriften Info-Feld leeren
        rowsModifyClearInfo: rowsModify(
          byAdrNrAnsNr: { usingAdrNr: {} }
          slowFilter: { isNotNull: {field: fldInfo} }
        ) {
          fldAnsNr
          fldInfo(set: {})
        }

        # Vorher erstellte Lieferanschrift ändern
        rowsModifyLi: rowsModify(
          byAdrNrAnsNr: { usingAdrNrLiAnsNr: {} }
        ) {
          fldAnsNr
          fldInfo(set: { text: "Lieferanschrift" } as: TEXT )
        }
        
        # Bestehende Infoanschriften ändern
        rowsModifyIn: rowsModify(
          byAdrNrAnsNr: { usingAdrNrInAnsNr: {} }
        ) {
          fldAnsNr
          fldInfo(set: { text: "Infoanschrift" } as: TEXT )
        }       
        
        # Endzustand der Anschriften auflisten
        rowsReadAfter: rowsRead(
          byAdrNrAnsNr: { usingAdrNr: {} } 
        ) {
          fldAnsNr
          fldNamen
          fldStdReKz
          fldStdLiKz
          fldStdInKz
          fldInfo(as: TEXT)
        }
        
      }
    }
  }
}
```

#### 14.2.2 Der spezielle `set...`-Parameter bei `rowNew`

Anders als bei Leseoperationen verwendet `rowNew` in Verlinkungen `set...`-Parameter statt `by...`-Parameter. Diese definieren, welche Schlüsselfelder des neuen Datensatzes automatisch befüllt werden:

```graphql
lnkPostalAddresses {
  rowNew(setAdrNrAnsNr: usingAdrNr) {
    # Das Feld fldAdrNr wird automatisch mit dem Wert aus dem äußeren Kontext befüllt
    fldAdrNr  # Zeigt "10000" aus dem Beispiel oben
  }
}
```

#### 14.2.3 Der `using`-Parameter in Assign-Operationen

Bei `assign...`-Parametern steht ein spezieller `using`-Parameter zur Verfügung, der es ermöglicht, Datensätze aus dem Kontext oder als "aktueller" Datensatz zu referenzieren, ohne explizite Suchkriterien anzugeben.

##### 14.2.3.1 `using: context` in Verlinkungen

Bei `rowNew`, `rowCopy` und `rowModify` innerhalb von Verlinkungen kann der `using`-Parameter mit dem Wert `context` verwendet werden, wenn der Typ des assign-Parameters mit dem Typ des Datensatzes übereinstimmt, von dem die Verlinkung ausgeht:

**Beispiel mit `using: context`:**
```graphql
mutation {
  tblAddresses {
    rowRead(kf1AdrNr: { text: "10000" }) {
      # Adresse im äußeren Kontext
      lnkProjects {
        # assignAddress ist vom Typ Address, genau wie der äußere Kontext
        rowNew(assignAddress: { using: context }) {
          fldAdrNr  # Wird automatisch mit "10000" befüllt
          # Weitere Felder werden aus der Adresse im äußeren Kontext übernommen
        }
      }
    }
  }
}
```

**Funktionsweise von `using: context`:**
- Bezieht sich immer auf den Datensatz des übergeordneten Kontexts
- Nur verfügbar, wenn der Typ des assign-Parameters mit dem Typ des äußeren Row-Kontexts übereinstimmt
- Übernimmt automatisch relevante Feldwerte aus dem Kontext-Datensatz
- Vermeidet redundante Datensatzsuchen und verbessert die Performance

##### 14.2.3.2 `using: current` für spezielle Datensätze

Für Tabellen, die das Konzept eines "aktuellen" oder "Standard"-Datensatzes unterstützen (z.B. der aktuell angemeldete Benutzer), kann der `using`-Parameter mit dem Wert `current` verwendet werden:

**Beispiel mit `using: current` in `Row`-Assign:**
```graphql
mutation {
  tblContacts {
    rowNew(
      assignUser:  { using: current }
    ) {
      fldNr
    }
  }
}
```

**Funktionsweise von `using: current`:**
- Referenziert automatisch den "aktuellen" Datensatz der jeweiligen Tabelle
- Verfügbar für Tabellen mit einem Konzept von "aktuell" oder "Standard"
- Kann sowohl in Row-Assign-Parametern als auch in Feld-Assign-Parametern verwendet werden
- Vermeidet die Notwendigkeit, den aktuellen Datensatz explizit zu suchen

**Verwendung in Feld-Assigns:**
```graphql
mutation {
  tblProjects {
    rowNew(
      assignAddress: { kf1AdrNr: { text: "10000" } }
    ) {
      # aktuellen Benutzer als Sachbearbeiter
      fldSBzrNr(assignUser:  { using: current } ) 
      rowSBzrNr {
        fldAnmNa
      }
    }
  }
}
```

Diese Funktionalität vereinfacht die Arbeit mit Standard- oder Kontextdatensätzen erheblich, da keine expliziten Referenzen oder zusätzlichen Suchoperationen erforderlich sind.

#### 14.2.4 Warum keine Verlinkungen in schreibenden Row-Kontexten?

Verlinkungen sind in schreibenden Row-Kontexten nicht verfügbar. Ein Datensatz, der sich in Bearbeitung befindet, ist in einem instabilen Zustand. Die Erstellung verlinkter Datensätze erfordert jedoch einen stabilen, gespeicherten Ausgangsdatensatz, um die referenzielle Integrität zu gewährleisten.

### 14.3 Verweise auf einzelne Datensätze (`row...`)

#### 14.3.1 Unterschiedliche Rückgabetypen

Das Verhalten von `row...`-Verweisen unterscheidet sich je nach Row-Kontext:

**In `RowMutationRead`-Kontexten:**
```graphql
mutation {
  tblProducts {
    rowRead(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }) {
      rowWgrNr {  # Gibt ProductGroupRowMutationRead zurück
        fldBez
        # Weitere Mutation-Operationen möglich
        lnkProducts {
          rowsModify (
            byWgrNr: {usingWgrNr: {} }
          ) {            
            fldMemo(set: {text: "Test" } as: TEXT)
          }
        }
      }
    }
  }
}
```

**In schreibenden Row-Kontexten:**
```graphql
mutation {
  tblProducts {
    rowModify(exactMatch: { byNr: { kf1ArtNr: { string: "PROD-001" } } }) {
      rowWgrNr {  # Gibt ProductGroupRowQueryRead zurück
        fldBez
        # Nur Abfrage-Operationen möglich
        lnkProducts {
          rowsRead (
            byWgrNr: {usingWgrNr: {} }
          ) {            
            fldMemo(as:TEXT)
          }
        }
      }
    }
  }
}
```

#### 14.3.2 Technischer Hintergrund

Die Rückgabe von `RowQueryRead`-Typen in schreibenden Row-Kontexten verhindert zyklische Zugriffe. Würde ein `row...`-Verweis `RowMutationRead`-Typen zurückgeben, könnte man über weitere `lnk...`-Felder theoretisch zum ursprünglichen, gerade in Bearbeitung befindlichen Datensatz zurücknavigieren. Ein solcher Versuch würde zu einem Sperrkonflikt führen.

#### 14.3.3 Praktische Anwendung

Verweise eignen sich besonders zur Informationsbeschaffung während Schreiboperationen:

```graphql
mutation (
  $wgrBez: String = null
) {
  tblProducts {
    rowsModify {
      fldArtNr
      fldWgrNr      

      # Variable auf null setzen
      clearVariable: _any(value: null) @store(in: $wgrBez) 
      
      # Information aus verknüpften Daten abrufen
      rowWgrNr {
        fldBez @store(in: $wgrBez)
      }

      # Basierend auf diesen Informationen Entscheidungen treffen
      updateIfWgrBezExists: _if(expr:{
        and: [
          { isNotNull: { value: $wgrBez } }
          { ne: [ { value: $wgrBez } { value: "" } ] }
        ]
      }) {
        # Text zusammensetzen...
        buildString: _any(expr:{
          add: [
            { value: "Aktualisiert basierend auf Warengruppe: " }
            { value: $wgrBez }
          ]
        }) @store(in: $wgrBez)
      
        # ... und ins Memo schreiben
        fldMemo(set: { text: $wgrBez } as:TEXT )
      }
    }
  }
}
```

### 14.4 Verschachtelte Tabellen (`tbl...`)

#### 14.4.1 Umgekehrtes Verfügbarkeitsmuster

Verschachtelte Tabellen zeigen ein zu Verlinkungen genau umgekehrtes Verfügbarkeitsmuster:

- **In `RowMutationRead`**: Nur Leseoperationen möglich
- **In schreibenden Row-Kontexten**: Vollständige Schreiboperationen verfügbar

#### 14.4.2 Technische Grundlage

Verschachtelte Tabellen sind eigenständige Tabellen, die in Blob-Feldern des übergeordneten Datensatzes gespeichert sind. Die Schreibbarkeit der verschachtelten Tabelle hängt direkt von der Schreibbarkeit des übergeordneten Blob-Feldes ab:

- Übergeordneter Datensatz in schreibendem Row-Kontext → Blob-Feld schreibbar → Verschachtelte Tabelle schreibbar
- Übergeordneter Datensatz im Lese-Kontext → Blob-Feld nur lesbar → Verschachtelte Tabelle nur lesbar

#### 14.4.3 Arbeiten mit verschachtelten Tabellen

```graphql
mutation {
  tblClient {
    rowModify {
      # Im schreibenden Row-Kontext
           
      tblBnkVb {
        
        # Startzustand der Einträge anzeigen
        before: rowsRead {
          fldNr
          fldIBAN
          fldGspKz
        }
               
        # Bestehende Einträge ändern: alle sperren
        rowsModify {
          fldNr
          fldGspKz(set: {boolean: true})
        }

        # Neue Bankverbindung hinzufügen
        rowNew {
          fldNr # null           
          fldIBAN(set: { text: "DE12345678901234567890" })
          rowSave {
            fldNr # Wurde automatisch vergeben
            fldGspKz # nicht gesperrt
          }
        }       

        # Endzustand der Einträge anzeigen
        after: rowsRead {
          fldNr
          fldIBAN
          fldGspKz
        }

      }
    }
  }
}
```

Änderungen in der verschachtelten Tabelle werden als Teil der übergeordneten Transaktion behandelt. Sie werden gemeinsam mit dem übergeordneten Datensatz gespeichert oder zurückgerollt.

### 14.5 Komplexe Workflows durch Verschachtelung

#### 14.5.1 Kombinationsmöglichkeiten

Die verschiedenen Beziehungstypen können beliebig kombiniert werden, wobei die Verfügbarkeitsregeln aus Abschnitt 14.1 stets gelten. Es gibt keine technische Begrenzung der Verschachtelungstiefe.

#### 14.5.2 Typische Muster

**Aufbau vollständiger Datenstrukturen:**
```graphql
mutation {
  tblAddresses {
    # Neue Adresse 
    rowNewCustomer: rowNew {
      fldStatus # Kunde
      rowSave {        
        fldAdrNr # automatisch zugewiesen: 10000+
        
        lnkPostalAddresses {
         
          rowNewBillingAddress: rowNew (setAdrNrAnsNr: usingAdrNr) {
            fldAdrNr # automatisch zugewiesen
            fldNa2(set:{text:"Rechnungsanschrift"})
            fldStdReKz(set:{boolean: true})
            rowSave {
              fldAnsNr # automatisch zugewiesen
              
              lnkContactPeople {
                rowNewA: rowNew (setAdrNrAnsNrNa:usingAdrNrAnsNr) {
                  fldAdrNr # automatisch zugewiesen
                  fldAnsNr # automatisch zugewiesen
                  fldAnr(set:{text:"Herr"})
                  fldVNa(set:{text:"Hubert"})
                  fldNNa(set:{text:"Hauptmann"})                  
                  rowSave {
                    fldAspNr # automatisch zugewiesen
                  }
                }
                rowNewB: rowNew (setAdrNrAnsNrNa:usingAdrNrAnsNr) {
                  fldAdrNr # automatisch zugewiesen
                  fldAnsNr # automatisch zugewiesen
                  fldAnr(set:{text:"Frau"})
                  fldVNa(set:{text:"Fiona"})
                  fldNNa(set:{text:"Fröhlich"})                  
                  rowSave {
                    fldAspNr # automatisch zugewiesen
                  }
                }          
              }
            }
          }
          
          rowNewShippingAddress: rowNew (setAdrNrAnsNr: usingAdrNr) {
            fldAdrNr # automatisch zugewiesen
            fldNa2(set:{text:"Lieferanschrift"})
            fldStdLiKz(set:{boolean: true})
            rowSave {
              fldAnsNr # automatisch zugewiesen
              
              lnkContactPeople {
                rowNew (setAdrNrAnsNrNa:usingAdrNrAnsNr) {
                  fldAdrNr # automatisch zugewiesen
                  fldAnsNr # automatisch zugewiesen
                  fldAnr(set:{text:"Herr"})
                  fldVNa(set:{text:"Max"})
                  fldNNa(set:{text:"Musterman"})                  
                  rowSave {
                    fldAspNr # automatisch zugewiesen
                  }
                }
              }
            }
          }
        }
      }
    }
    
    rowNewSupplier: rowNew {
      fldStatus(set:{text:"Lieferant"})
      rowSave {
        # Automatisch zugewiesene Felder
        fldAdrNr # automatisch zugewiesen: 70000+
        #lnkPostalAddresses { ...
      }
    }    
  }
}
```

**Intelligente Datenverarbeitung:**
```graphql
mutation (
  $wgrBez: String = null
) {
  tblProducts {
    rowsModify (
      fastFilter: { isNotNull: { field: fldWgrNr } }
    ) {
      fldArtNr
      fldWgrNr      

      # Variable auf null setzen
      clearVariable: _any(value: null) @store(in: $wgrBez) 
      
      # Warengruppen Bezeichnung lesen
      rowWgrNr {
        fldBez @store(in: $wgrBez)
      }

      # Wenn die Warengruppen Bezeichnung +++ enthält
      updateIfWgrBezExists: _if(expr:{
        gt: [
          { fnPos: [ { value: "+++" } { value: $wgrBez } ] }
          { value: 0}
        ]
      }) {
        # Rabattsatz 0 für Verkaufspreis 0 auf 10% setzen  
        fldVk0_Rab0_Sz(set:{float:10.0})        
      }
    }
  }
}
```

#### 14.5.3 Reihenfolge und Abhängigkeiten

Die sequenzielle Abarbeitung von GraphQL-Operationen ermöglicht eine präzise Kontrolle über die Ausführungsreihenfolge. Explizite Speicheroperationen (`rowSave`, `rowSaveAndModify`) werden sofort ausgeführt, wodurch nachfolgende Operationen auf die aktualisierten Daten zugreifen können.

Nach einer expliziten Speicherung ändern sich die verfügbaren Operationen:
- Nach `rowSave`: Wechsel zu `RowMutationRead`, `lnk...` verfügbar, keine Feldänderungen mehr
- Nach `rowSaveAndModify`: Verbleib in schreibendem Row-Kontext, weitere Feldänderungen möglich, aber keine `lnk...`

### 14.6 Best Practices für komplexe Strukturen

#### 14.6.1 Strukturierte Planung

Bei der Entwicklung komplexer Mutationen empfiehlt sich eine strukturierte Herangehensweise:

1. Identifikation der Abhängigkeiten zwischen Datensätzen
2. Planung der Erstellungsreihenfolge (vom Hauptdatensatz zu abhängigen Strukturen)
3. Bestimmung der Stellen, an denen explizite Speicherungen erforderlich sind
4. Gruppierung logisch zusammengehöriger Operationen

#### 14.6.2 Fehlerrobustheit

Das Transaktionsmodell von Mutationen gewährleistet, dass entweder alle Änderungen erfolgreich durchgeführt oder komplett zurückgerollt werden. Diese Atomizität sollte genutzt werden, um konsistente Datenstrukturen zu gewährleisten.

#### 14.6.3 Performance-Überlegungen

Während die Verschachtelungstiefe technisch unbegrenzt ist, sollten sehr tiefe Verschachtelungen vermieden werden:
- Sie erschweren die Nachvollziehbarkeit
- Sie können die Fehleranalyse verkomplizieren
- Längere Transaktionen erhöhen die Wahrscheinlichkeit von Sperrenkonflikten

Die detaillierte Behandlung von Performance-Aspekten und Sperrenverwaltung erfolgt im nächsten Kapitel.

## 15. Transaktionssperren und Optimierung

Dieses Kapitel behandelt die fortgeschrittenen Aspekte der Performance-Optimierung von GraphQL-Mutationen. Nach den praktischen Grundlagen der vorherigen Kapitel liegt der Fokus nun auf dem Verständnis und der strategischen Nutzung des Sperrsystems zur Entwicklung robuster und effizienter Anwendungen.

### 15.1 Das Sperrsystem verstehen

#### 15.1.1 Zwei unabhängige Sperrtypen

Die zugrundeliegende Datenbank-Engine arbeitet mit zwei voneinander unabhängigen Sperrtypen:

**Transaktionssperren** werden auf Tabellenebene vergeben und gehören zu einer spezifischen Transaktion. Sie koordinieren den Zugriff zwischen verschiedenen Transaktionen und werden automatisch beim Festschreiben oder Zurückrollen der Transaktion freigegeben.

**Datensatzsperren** schützen einzelne Datensätze während ihrer Bearbeitung. Im GraphQL-Kontext werden diese automatisch beim Öffnen eines Datensatzes gesetzt. In der regulären microtech ERP-Anwendung können solche Sperren jedoch deutlich länger bestehen - beispielsweise solange ein Benutzer einen Datensatz in einer Eingabemaske geöffnet hat.

#### 15.1.2 Lesesperren und Schreibsperren

Beide Sperrtypen existieren in zwei Ausprägungen:

- **Lesesperren** (Shared Locks): Mehrere Transaktionen können gleichzeitig Lesesperren auf derselben Ressource halten
- **Schreibsperren** (Exclusive Locks): Nur eine Transaktion kann eine Schreibsperre halten, sie schließt alle anderen Zugriffe aus

Für eine bestimmte Ressource können entweder mehrere Lesesperren oder eine einzelne Schreibsperre existieren, niemals beides gleichzeitig.

#### 15.1.3 Relevanz für GraphQL-Mutationen

Für die Performance-Optimierung von GraphQL-Mutationen sind primär die Transaktionssperren relevant. Diese werden automatisch vergeben:
- Lesende Operationen setzen Lesesperren auf die betroffene Tabelle
- Schreibende Operationen setzen Schreibsperren auf die betroffene Tabelle

Diese Sperren bleiben für die gesamte Dauer der Transaktion bestehen.

### 15.2 Das Sperrerweiterungsproblem

#### 15.2.1 Interne Struktur von GraphQL-Operationen

Eine scheinbar einfache GraphQL-Operation wie `rowModify` besteht intern aus mehreren Datenbankoperationen:

1. **Suchen des Datensatzes** (lesende Operation) → Lesesperre auf Tabelle
2. **Öffnen zur Bearbeitung** (lesende Operation mit Datensatzsperre) → Lesesperre bleibt
3. **Speichern der Änderungen** (schreibende Operation) → Benötigt Schreibsperre

Der kritische Punkt ist Schritt 3: Die Transaktion hält bereits eine Lesesperre und benötigt nun eine Schreibsperre auf derselben Tabelle. Diese Erweiterung von Lese- zu Schreibsperre ist eine Hauptquelle für Performance-Probleme.

#### 15.2.2 Entstehung von Deadlocks

Ein Deadlock entsteht, wenn zwei oder mehr Transaktionen gegenseitig auf Ressourcen warten, die die jeweils andere Transaktion hält. Bei GraphQL-Mutationen ist folgendes Szenario typisch:

1. Transaktion A führt `rowModify` auf Tabelle X aus → erhält Lesesperre
2. Transaktion B führt `rowModify` auf Tabelle X aus → erhält ebenfalls Lesesperre
3. Transaktion A will speichern → benötigt Schreibsperre, wartet auf B
4. Transaktion B will speichern → benötigt Schreibsperre, wartet auf A
5. Deadlock erkannt → Eine Transaktion wird abgebrochen

#### 15.2.3 Automatische Deadlock-Auflösung

Die Datenbank-Engine erkennt Deadlocks automatisch und löst sie durch Auswahl eines "Opfers" auf. Die gewählte Transaktion wird abgebrochen, ihre Sperren werden freigegeben, und alle ihre Änderungen werden zurückgerollt. Die andere Transaktion kann dann fortfahren.

Für die abgebrochene GraphQL-Mutation bedeutet dies einen Laufzeitfehler und den Verlust aller bereits durchgeführten Arbeiten.

### 15.3 Lösung durch präventive Sperrenvergabe

#### 15.3.1 Die `@acquireLocks`-Direktive

Die `@acquireLocks`-Direktive löst das Sperrerweiterungsproblem durch präventive Sperrenvergabe:

```graphql
mutation @acquireLocks(
  forWriting: [tblAddresses, tblPostalAddresses],
  forReading: [tblProducts]
) {
  # Mutation wird nur ausgeführt, wenn alle Sperren verfügbar sind
  tblAddresses {
    rowModify(...) { ... }
  }
}
```

#### 15.3.2 Funktionsweise

Die Direktive versucht, alle angegebenen Sperren atomar zu erhalten, bevor die eigentliche Mutation startet:

- **Erfolg**: Alle Sperren sind verfügbar → Mutation wird ausgeführt
- **Misserfolg**: Nicht alle Sperren verfügbar → Sofortiger Abbruch mit Zeitüberschreitungsfehler

Da alle benötigten Schreibsperren bereits zu Beginn vorhanden sind, sind keine späteren Sperrerweiterungen mehr nötig. Dies eliminiert die Hauptursache für Deadlocks.

#### 15.3.3 Parameter im Detail

- **`forWriting`**: Liste von Tabellen, für die Schreibsperren benötigt werden
- **`forReading`**: Liste von Tabellen, für die nur Lesesperren benötigt werden

Beide Parameter sind optional. Wird eine Tabelle in beiden Listen angegeben, hat `forWriting` Vorrang. Die Sperren werden in einer konsistenten Reihenfolge angefordert, wodurch auch Deadlocks zwischen verschiedenen `@acquireLocks`-Operationen vermieden werden.

### 15.4 Optimierungsstrategien

#### 15.4.1 Wann `@acquireLocks` verwenden?

Die Empfehlung ist eindeutig: **Verwenden Sie `@acquireLocks` bei allen Schreiboperationen.** Dies mag zunächst übertrieben erscheinen, aber selbst eine einzelne `rowModify`-Operation kann Deadlocks verursachen.

Besonders wichtig ist die Direktive bei:
- Hochfrequenten Operationen
- Operationen auf häufig genutzten Tabellen
- Komplexen Workflows mit mehreren Tabellen
- Operationen mit expliziten Leseoperationen vor Schreibzugriffen

#### 15.4.2 Richtige Spezifikation der Sperren

Analysieren Sie Ihre Mutation und identifizieren Sie alle betroffenen Tabellen:

```graphql
mutation @acquireLocks(
  forWriting: [
    tblAddresses,        # rowModify hier
    tblPostalAddresses,  # rowModify über lnkPostalAddresses
    tblContacts          # rowNew über lnkContacts
  ],
  forReading: [
    tblProductGroups,    # Gelesen über assignProductGroup
    tblUsers             # Gelesen über assignUser
  ]
) {
  tblAddresses {
    rowModify(...) {
      # ...
      rowSave {
        lnkPostalAddresses {
          rowModify(...) { ... }
        }
        lnkContacts {
          rowNew(...) { ... }
        }
      }
    }
  }
}
```

Beachten Sie auch implizite Lesezugriffe durch:
- `row...`-`assign...`-Parameter (lesen aus der referenzierten Tabelle)
- `fld...`-`assign...`-Parameter (lesen aus der referenzierten Tabelle)
- Verweise über `row...`-Felder

Diese sollten alle in der `@acquireLocks`-Strategie berücksichtigt werden, um unerwartete Sperrenkonflikte zu vermeiden.

#### 15.4.3 Balance zwischen Sicherheit und Performance

Während `@acquireLocks` Deadlocks verhindert, kann übermäßige Sperrung die Parallelität reduzieren. Einige Richtlinien:

- Spezifizieren Sie nur Tabellen, auf die tatsächlich geschrieben wird
- Verwenden Sie `forReading` für Tabellen, die nur gelesen werden
- Halten Sie Transaktionen so kurz wie möglich
- Vermeiden Sie unnötige Operationen innerhalb der Transaktion

### 15.5 Fehlerbehandlung und Diagnose

#### 15.5.1 Typische Fehlermuster

**Deadlock-Fehler** haben charakteristische Meldungen und deuten auf fehlende präventive Sperrung hin. Sie treten typischerweise bei paralleler Last auf.

**Zeitüberschreitungsfehler** beim Anfordern von Sperren können auf verschiedene Ursachen hinweisen:
- Hohe Systemlast
- Lang laufende andere Transaktionen
- Unzureichende Timeout-Konfiguration

### 15.5.2 Systematische Analyse

Bei wiederkehrenden Problemen empfiehlt sich:

1. **Identifikation problematischer Tabellen** durch Analyse der Fehlermeldungen
2. **Überprüfung der Zugriffsmuster** in betroffenen Mutationen
3. **Erweiterung der `@acquireLocks`-Strategien** für diese Tabellen
4. **Monitoring der Verbesserungen** nach den Anpassungen

### 15.6 Best Practices

#### 15.6.1 Entwicklungsstandards

Etablieren Sie klare Standards für Ihr Entwicklungsteam:

- Alle Mutationen mit Schreiboperationen verwenden `@acquireLocks`
- Dokumentation der Sperrenstrategie in Kommentaren
- Konsistente Muster für ähnliche Operationen
- Regelmäßige Reviews der Sperrenstrategien

#### 15.6.2 Testen unter Last

Deadlock-Probleme zeigen sich oft erst unter paralleler Last. Integrieren Sie Lasttests in Ihren Entwicklungsprozess:

- Simulieren Sie realistische Parallelität
- Testen Sie Kombinationen verschiedener Mutationen
- Überwachen Sie Performance-Metriken
- Identifizieren Sie Engpässe frühzeitig

#### 15.6.3 Kontinuierliche Optimierung

Performance-Optimierung ist ein fortlaufender Prozess:

- Überwachen Sie Produktivsysteme auf Sperrenprobleme
- Analysieren Sie neue Geschäftsprozesse auf potenzielle Konflikte
- Passen Sie Sperrenstrategien bei Bedarf an
- Dokumentieren Sie Erkenntnisse für zukünftige Entwicklungen

### 15.7 Zusammenfassung

Die korrekte Verwendung von `@acquireLocks` ist essentiell für robuste GraphQL-Mutationen. Durch präventive Sperrenvergabe werden Deadlocks vermieden und die Gesamtperformance des Systems verbessert. Die kleine zusätzliche Komplexität in der Entwicklung zahlt sich durch stabilere und vorhersagbarere Anwendungen aus.

## Weitere Ressourcen

!!! tip "Tipp"

    Auf folgender Seite erhalten Sie praktische Tipps für Beispiel-Abfragen:

    *    **[GraphQL Beispiel Queries](../18_graphql/graphqlbeispielqueries.md)**