# Mappings (mappings)

> Die mappings-Tabelle definiert, welche Felder wie zwischen Quell- und Zielsystem übertragen und transformiert werden.

---

Site [llms.txt](/llms.txt)

---

Die `mappings`-Tabelle ist die mächtigste Konfigurationsebene der DataBridge.
Sie legt für jedes Feld fest: woher kommt der Wert, wohin geht er, und wie wird
er unterwegs transformiert.

Ein Mapping ist eine **Gruppe von Zeilen** in der Tabelle — alle mit demselben
`PartitionKey`. Jede Zeile beschreibt genau eine Feld-Zuordnung.

---

## Tabellenstruktur

| Spalte | Pflicht | Beschreibung |
|---|:---:|---|
| **PartitionKey** | ✅ | Name des Mappings. Wird im Job unter `Mapping` referenziert. Im Gegensatz zu `jobs` und `connections` ist der PartitionKey hier **frei wählbar** — er dient der Gruppierung. |
| **RowKey** | ✅ | Eindeutiger Bezeichner innerhalb des Mappings. Konvention: `_quellfeld__zielfeld_` |
| **SourceTable** | ✅ | Name der Quelltabelle/-entität. Muss innerhalb eines Mappings konsistent sein. |
| **SourceField** | — | Name des Quellfeldes. Kann leer sein (bei `DefaultValue`) oder mit `_` beginnen — dann handelt es sich um ein [virtuelles Feld](#virtuelle-felder). |
| **TargetTable** | ✅ | Name der Zieltabelle/-entität. Muss innerhalb eines Mappings konsistent sein. |
| **TargetField** | ✅ | Name des Zielfeldes. Muss dem tatsächlichen API-/Spaltenname entsprechen. |
| **TargetKey** | — | JSON-Objekt. Definiert, wie ein bestehender Datensatz im Zielsystem identifiziert wird (für Upsert-Logik). |
| **SourceOperation** | — | JSON-Objekt der Form `{ "Operations": [ … ] }`. Transformation beim **Lesen** aus der Quelle. |
| **TargetOperation** | — | JSON-Objekt der Form `{ "Operations": [ … ] }`. Transformationen beim **Schreiben** ins Ziel. |

### RowKey-Namenskonvention

Die empfohlene Konvention für den RowKey: `_quellfeld__zielfeld_`

Beispiele:
```
_systemid__wysa_bcsystemid_
_lastModifiedDateTime__wysa_lastmodified_
_no__wysa_bcnumber_
```

Doppelter Unterstrich trennt Quell- vom Zielfeld. Die umgebenden Unterstriche
kennzeichnen es als Mapping-RowKey. Diese Konvention ist optional, aber empfohlen
für Lesbarkeit und Filterbarkeit.

---

## TargetKey — Upsert-Schlüssel

Der `TargetKey` bestimmt, wie die DataBridge entscheidet, ob ein Datensatz im
Zielsystem **angelegt** (Insert) oder **aktualisiert** (Update) wird.

Es gibt vier Typen:

### PrimaryKey

Nutzt den systemnativen Primärschlüssel des Zielsystems. Der Schlüsselname wird
automatisch aus dem `SourceField` der Mapping-Zeile übernommen.

```json
{ "KeyType": "PrimaryKey" }
```

### Id

Ähnlich wie `PrimaryKey`, nutzt jedoch die systemspezifische ID-Eigenschaft des Connectors.

```json
{ "KeyType": "Id" }
```

> **Unterschied zwischen PrimaryKey und Id**
>
> Bei Business Central und NAV entscheidet die Wahl über die Anführungszeichen im
> erzeugten OData-Schlüssel: `PrimaryKey` setzt den Wert in Anführungszeichen, `Id`
> schreibt ihn unquotiert. Die falsche Wahl führt zu einem Syntaxfehler in der Abfrage.
> 
> Dataverse kennt nur `PrimaryKey`. Ein dort eingetragenes `Id` fällt in den
> Standardzweig, erzeugt lediglich eine **Warnung** im Log und wird übersprungen —
> kein Fehler, aber der Schlüssel greift nicht.



### CustomKey

Benutzerdefinierter Schlüssel aus einem oder mehreren Feldern. Wird eingesetzt, wenn
der Datensatz im Zielsystem nicht über den Primärschlüssel, sondern über eine
Geschäftsfeld-Kombination identifiziert wird.

```json
{
  "KeyType": "CustomKey",
  "KeyName": "wysa_number",
  "KeyValues": [
    { "keyName": "wysa_segmentno", "attributeName": "segmentNo", "rank": 1 },
    { "keyName": "wysa_lineno",    "attributeName": "lineNo",    "rank": 2 }
  ]
}
```

| Feld | Bedeutung bei `CustomKey` |
|---|---|
| `KeyName` | Zielfeld, das den zusammengesetzten Schlüsselwert enthält |
| `keyName` | **Zielfeld**-Name für diesen Schlüsselteil |
| `attributeName` | **Quellfeld**-Name, aus dem der Wert kommt |
| `StaticValue` | Fester Wert statt eines Quellwertes (siehe unten) |
| `rank` | Reihenfolge der Schlüsselteile innerhalb des Schlüssels (aufsteigend) |

### Filter

Sucht im Zielsystem nach einem Datensatz, der alle angegebenen Feld-Wert-Kombinationen
erfüllt. Im Gegensatz zu `CustomKey` wird kein explizites Schlüsselfeld benötigt.

```json
{
  "KeyType": "Filter",
  "KeyValues": [
    { "keyName": "mailingGroupCode", "attributeName": "listid",   "rank": 1 },
    { "keyName": "contactNo",        "attributeName": "entityid", "rank": 2 }
  ]
}
```

> **Bei Dataverse ist die Bedeutung von keyName und attributeName vertauscht**
>
> Im D365CE-Connector haben die beiden Felder bei `KeyType: Filter` die **umgekehrte**
> Bedeutung wie bei `CustomKey`:
> 
> | Feld | Bedeutung bei `Filter` (D365CE) |
> |---|---|
> | `keyName` | **Quellfeld** — daraus wird der Suchwert gelesen |
> | `attributeName` | **Zielfeld** — dessen Metadaten bestimmen die Typumwandlung |
> 
> Bei Business Central gilt dagegen für **beide** Schlüsseltypen dieselbe Zuordnung
> (`keyName` = Zielfeld).



> **Stiller Fallstrick bei Filter in Dataverse**
>
> Die Bedingung wird nur dann in die Abfrage aufgenommen, wenn im selben Mapping eine
> Zeile existiert, deren `SourceField` dem `keyName` **und** deren `TargetField` dem
> `attributeName` entspricht.
> 
> Fehlt diese Zeile, wird die Bedingung **kommentarlos weggelassen** — die Abfrage
> liefert dann zu viele Datensätze, ohne dass ein Fehler auftritt.



### Statische Schlüsselwerte {#staticvalue}

*(ab v2.1 — verfügbar für D365CE und D365BC)*

Ein Schlüsselteil kann gegen einen fest hinterlegten Wert aufgelöst werden, ohne dass
dafür ein Quellfeld existieren muss. Dazu wird statt `attributeName` das Feld
`StaticValue` gesetzt:

```json
{
  "KeyType": "CustomKey",
  "KeyName": "wysa_category",
  "KeyValues": [
    { "keyName": "wysa_category", "StaticValue": "STANDARD", "rank": 1 }
  ]
}
```

`StaticValue` greift **nur**, wenn `attributeName` leer ist. Ist beides gesetzt, gewinnt
der Quellwert.

Bei Business Central werden statische Werte für die Metadaten-Typen `string`, `guid` und
`int32` unterstützt; andere Typen führen zu einem Fehler.

> **Besteht der Schlüssel nur aus statischen Werten**
>
> Dann wird die Mapping-Zeile auch dann verarbeitet, wenn das `SourceField` in der
> Nachricht gar nicht vorkommt — die Zeile wird also nicht wie sonst übersprungen.



> **D365NAV unterstützt keine statischen Schlüsselwerte**
>
> Der NAV-Connector greift bei der Schlüssel- und Filtererzeugung unbedingt auf
> `attributeName` zu. Ein dort gesetztes `StaticValue` bleibt wirkungslos und führt zu
> einem Fehler, sobald das Quellfeld fehlt.



### Mehrere Ziel-Schlüssel mit Rangfolge {#keyrank}

*(ab v2.1)*

Ein Mapping kann mehrere Ziel-Schlüssel besitzen. Sie werden anhand des Feldes `KeyRank`
in **aufsteigender** Reihenfolge geprüft — sobald ein Schlüssel im Zielsystem einen
Datensatz findet, wird dieser verwendet und die übrigen werden übersprungen.

Typischer Einsatz: zuerst über eine externe ID suchen, ersatzweise über eine Nummer.

Die Konfiguration unterscheidet sich je nach Ebene:

| Ebene | Vorgehen |
|---|---|
| **Table-Mapping** | Je Schlüssel eine **eigene Mapping-Zeile** mit eigenem `TargetKey`-JSON anlegen. Die DataBridge fasst alle `TargetKey`-Einträge einer Mapping-Gruppe zusammen und sortiert sie nach `KeyRank`. |
| **Lookup** (TargetOperation) | In `Parameters` statt `TargetKey` ein Array `TargetKeys` mit mehreren Schlüsseln angeben. |

```json
{
  "KeyType": "CustomKey",
  "KeyRank": 1,
  "KeyName": "wysa_externalid",
  "KeyValues": [
    { "keyName": "wysa_externalid", "attributeName": "externalId", "rank": 1 }
  ]
}
```

> **KeyRank und rank nicht verwechseln**
>
> `KeyRank` bestimmt die Reihenfolge **mehrerer Schlüssel** zueinander.
> `rank` innerhalb von `KeyValues` bestimmt die Reihenfolge **der Felder innerhalb eines
> Schlüssels**.



Bestehende Mappings mit einem einzelnen `TargetKey` funktionieren unverändert — intern
wird ein einzelner Schlüssel wie eine Liste mit einem Eintrag behandelt.

> **Leere Schlüssel in der Quelle**
>
> Liefert die Quelle für einen Schlüssel keinen Wert, bricht die Verarbeitung der Nachricht
> standardmäßig mit einem Fehler ab — auch dann, wenn ein weiterer Schlüssel den Datensatz
> noch hätte finden können. Mit [`IgnoreEmptySourceKey`](#ignoreemptysourcekey) lässt sich
> das je Schlüssel abschalten.



### KeyAttributeFromExisting

Optionales Feld am `TargetKey`, ausgewertet bei `KeyType: Filter` von den Konnektoren für
Business Central, NAV und OpenAPI. Der Filter findet nur den bestehenden Zieldatensatz.
Für die Item-URL beim Aktualisieren (bei OpenAPI auch beim Löschen) wird der Schlüssel
aus dem hier angegebenen Feld dieses Datensatzes übernommen.

- **BC/NAV:** Pflicht bei `Filter`. Guid- und Zahl-Schlüssel werden ohne, alle anderen mit
  Hochkommas in die URL geschrieben, der Typ kommt aus den Metadaten. Fehlt das Feld im
  bestehenden Datensatz, bricht die Verarbeitung mit einem Fehler ab.
- **OpenAPI:** Optional. Ohne Angabe wird der Primärschlüssel der Spezifikation verwendet
  (`id`, `Id`, `ID`, `uuid` oder `key`).

Der Feldname ist case-sensitiv (`id` ≠ `Id`).

### IgnoreEmptySourceKey {#ignoreemptysourcekey}

*(ab v2.2, nur D365CE)*

Optionales Boolesches Feld am `TargetKey`. Standard ist `false`.

```json
{
  "KeyType": "CustomKey",
  "KeyRank": 2,
  "KeyName": "wysa_externalid",
  "IgnoreEmptySourceKey": true,
  "KeyValues": [
    { "keyName": "wysa_externalid", "attributeName": "externalId", "rank": 1 }
  ]
}
```

#### Zwei verschiedene Arten von „kein Treffer"

Um das Feld einordnen zu können, muss man zwei Situationen auseinanderhalten, die beide
danach aussehen, als wäre ein Schlüssel erfolglos geblieben:

| Situation | Was passiert | Verhalten ohne dieses Feld |
|---|---|---|
| **Schlüssel lässt sich nicht aufbauen** — das Quellfeld ist leer, es gibt gar keinen Wert zum Suchen | Es wird nicht gesucht | **Fehler**, die Nachricht schlägt fehl |
| **Schlüssel findet nichts** — es wurde gesucht, im Zielsystem existiert der Datensatz noch nicht | Suche läuft, bleibt ergebnislos | **Datensatz wird angelegt** — kein Fehler |

Die zweite Zeile ist der Normalfall für jeden neuen Datensatz: Beim ersten Übertragen
findet kein Schlüssel etwas, und genau deshalb wird angelegt. `IgnoreEmptySourceKey`
ändert daran **nichts**.

Das Feld wirkt ausschliesslich auf die **erste** Zeile: Es erklärt einen leeren Quellwert
für diesen einen Schlüssel zum Normalfall statt zum Fehler.

> **Warum ein leerer Schlüssel überhaupt ein Fehler ist**
>
> Bei einem leeren Schlüssel konnte nicht geprüft werden, ob der Zieldatensatz existiert.
> Ihn trotzdem anzulegen, könnte ein Duplikat erzeugen. Deshalb bricht die DataBridge im
> Zweifel ab — es sei denn, Sie erklären mit diesem Feld, dass der Schlüssel erwartbar
> leer sein kann.



#### Wann das Feld überhaupt zum Tragen kommt

Die Schlüssel werden nach `KeyRank` der Reihe nach geprüft. **Sobald ein Schlüssel einen
Datensatz findet, bricht die Suche ab und der Datensatz wird aktualisiert** — unabhängig
davon, was bei den vorherigen Schlüsseln passiert ist und wie sie konfiguriert sind.

Beispiel mit drei Schlüsseln, bei dem Schlüssel 1 leer und **nicht** markiert ist:

| Schritt | Ergebnis |
|---|---|
| Schlüssel 1 | leer, wird übersprungen — gemerkt: „hier war ein Fehler" |
| Schlüssel 2 | leer, markiert — wird übersprungen |
| Schlüssel 3 | **findet den Datensatz** → Suche endet |
| Ergebnis | **Update.** Kein Fehler — der gemerkte Fehler wird gar nicht mehr ausgewertet |

Das Feld greift also **nur dann**, wenn die Liste vollständig durchlaufen wurde und
**kein einziger** Schlüssel einen Datensatz gefunden hat. Erst dann steht die Frage
„Fehler oder anlegen?" überhaupt im Raum.

#### Die Entscheidung am Ende

Hat kein Schlüssel etwas gefunden, entscheidet sich Folgendes:

- Waren **alle** leer gebliebenen Schlüssel mit `IgnoreEmptySourceKey: true` markiert
  (oder blieb gar keiner leer), wird ein **neuer Datensatz angelegt**.
- War auch nur **einer** der leer gebliebenen Schlüssel **nicht** markiert, bleibt es
  beim **Fehler**.

Das Feld gilt also je Schlüssel: Es hebt die Fehlerbehandlung der übrigen Schlüssel nicht
auf. Lassen Sie es bei einem Schlüssel bewusst weg, bleibt ein leerer Wert dort ein
Fehler — auch wenn ein anderer Schlüssel des Mappings markiert ist.

> **Das Feld bewusst setzen**
>
> Für die betroffenen Fälle verschiebt sich das Verhalten von „Nachricht abweisen" zu
> „Datensatz anlegen". Setzen Sie es nur dort, wo ein leerer Quellwert fachlich erwartbar
> ist und ein weiterer Schlüssel den Datensatz zuverlässig findet.



#### Typischer Einsatz

Ein optionales Feld wie eine externe ID ist nicht in jedem Quelldatensatz gefüllt, die
Belegnummer dagegen immer. Mit `IgnoreEmptySourceKey: true` am ID-Schlüssel laufen auch
die Datensätze ohne externe ID sauber durch und werden über die Belegnummer gefunden.

Das Feld wird derzeit ausschliesslich vom **D365CE**-Konnektor ausgewertet. Bei Business
Central und NAV bleibt es wirkungslos.

---

## Virtuelle Felder {#virtuelle-felder}

Beginnt das `SourceField` mit einem Unterstrich, handelt es sich um ein **virtuelles
Feld**: Es wird nicht aus der Quelltabelle gelesen, sondern zur Laufzeit über eine
`SourceOperation` erzeugt.

> **SourceOperations greifen ausschliesslich bei virtuellen Feldern**
>
> Eine `SourceOperation` wird **nur** ausgeführt, wenn das `SourceField` der Zeile mit `_`
> beginnt. Steht dort ein normaler Feldname, wird die Operation stillschweigend ignoriert —
> das gilt für `LookupValue`, `ActivityParty`, `LinkToRecord` und `ReadFromMessage`
> gleichermaßen.
> 
> Umgekehrt wird ein `_`-präfixiertes Feld nicht in die Select-Liste der Quellabfrage
> aufgenommen — stattdessen wird das unter `Parameters.SourceField` genannte Feld
> abgefragt.



Der ResponseJob ist dabei nur **einer** von mehreren Anwendungsfällen:

| Anwendungsfall | Operation |
|---|---|
| Wert aus einer anderen Tabelle nachschlagen | `LookupValue` |
| Wert aus einer ActivityParty-Beziehung lesen | `ActivityParty` |
| Link auf einen Dataverse-Datensatz erzeugen | `LinkToRecord` |
| Wert aus der Antwortnachricht lesen (ResponseJob) | `ReadFromMessage` |
| Zeilen eines Kind-Jobs einbetten | `EmbedChildren` |

---

## SourceOperation — Transformation beim Lesen

SourceOperations werden angewendet, **bevor** der Wert in die Service Bus Queue
geschrieben wird. Sie sind connector-spezifisch — nicht jede Operation steht in jedem
Connector zur Verfügung.

> **Es wird nur die erste Operation ausgeführt**
>
> Obwohl `Operations` ein Array ist, wertet die DataBridge bei SourceOperations
> ausschließlich den **ersten** Eintrag aus. Weitere Einträge werden verworfen — ohne
> Fehler und ohne Warnung.



### LookupValue
*(verfügbar: D365BC, D365CE, D365NAV)*

Liest einen Wert aus einer anderen Tabelle im Quellsystem nach.
Beispiel: Aus einem Land-Code soll der ausgeschriebene Ländername übertragen werden.

```json
{
  "Operations": [
    {
      "Name": "LookupValue",
      "Parameters": {
        "LookupType": "Attribute",
        "SourceField": "countryRegionCode",
        "LookupTable": "publisher/product/v1.0/countriesRegions",
        "LookupField": "name",
        "UseCache": true
      }
    }
  ]
}
```

| Parameter | Beschreibung |
|---|---|
| `LookupType` | `Attribute` — liest ein bestimmtes Feld aus dem gefundenen Datensatz. `Name` — liefert den Anzeigenamen des verknüpften Datensatzes; dann entfallen `LookupTable` und `LookupField`. |
| `SourceField` | Feld im Quelldatensatz, das den Suchwert enthält |
| `LookupTable` | API-Pfad der nachzuschlagenden Tabelle |
| `LookupField` | Feld im nachgeschlagenen Datensatz, dessen Wert übertragen wird |
| `UseCache` | `true` dringend empfohlen — siehe Hinweis |

> **LookupType Name nur in Dataverse**
>
> Der Wert `Name` ist ausschließlich im D365CE-Connector implementiert. Business Central
> und NAV werfen dafür einen Nicht-implementiert-Fehler.



> **UseCache aktiviert das Pre-Caching**
>
> Mit `UseCache: true` löst der Dataverse-Connector die benötigten Lookup-Werte seit v2.1
> **pro Batch gebündelt vorab** auf — in Blöcken von 1000 Werten — statt für jeden
> Datensatz einzeln beim Zielsystem nachzufragen. Das beschleunigt große Änderungsmengen
> erheblich.
> 
> Ohne `UseCache` entfällt das Pre-Caching vollständig. Vorausgesetzt wird zusätzlich ein
> gesetztes `Parameters.SourceField`.



### ReadFromMessage {#readfrommessage}
*(verfügbar: D365BC, D365NAV, D365CE)*

Liest einen Wert aus der ursprünglichen Queue-Nachricht — wird typischerweise in
**ResponseJobs** verwendet, um Felder aus der Antwort des Zielsystems (z. B. eine neu
vergebene ID) zurückzuschreiben.

```json
{
  "Operations": [
    { "Name": "ReadFromMessage" }
  ]
}
```

Zugehörige Zeile in der Tabelle:

| Spalte | Wert |
|---|---|
| SourceField | `_id` (Unterstrich-Präfix!) |
| TargetField | `wysa_bcid` |

### ActivityParty
*(verfügbar: nur D365CE)*

Liest ein Feld über eine ActivityParty-Beziehung aus. Wird für CE-Aktivitäten
(E-Mails, Aufgaben) verwendet, bei denen Teilnehmer über den `activityparty`-Datensatz
verknüpft sind.

```json
{
  "Operations": [
    {
      "Name": "ActivityParty",
      "Parameters": {
        "PartyParams": {
          "TypeMask": 2,
          "SourceField": "activityid",
          "TargetField": "wysa_number",
          "TargetTable": "contact"
        }
      }
    }
  ]
}
```

| Parameter | Beschreibung |
|---|---|
| `TypeMask` | Teilnehmertyp (siehe Hinweis) |
| `SourceField` | Feld mit der Activity-ID |
| `TargetField` | Feld im verknüpften Datensatz, das gelesen werden soll |
| `TargetTable` | Entität des verknüpften Datensatzes |

Werden mehrere Teilnehmer gefunden, werden ihre Werte komma-separiert zusammengefügt.

> **TypeMask ist ein Dataverse-Plattformwert**
>
> Die DataBridge reicht den Wert unverändert an Dataverse durch. Die Bedeutung der
> einzelnen Zahlen (1 = Sender, 2 = To-Empfänger, …) ist in der
> [Dataverse-Dokumentation von Microsoft](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/reference/entities/activityparty)
> beschrieben und nicht Teil dieser Anwendung.



### LinkToRecord
*(verfügbar: nur D365CE)*

Erzeugt eine anklickbare URL zu einem Datensatz in Dataverse.
Nützlich, um in BC oder SQL einen direkten Link auf den zugehörigen CE-Datensatz zu
speichern.

```json
{
  "Operations": [
    {
      "Name": "LinkToRecord",
      "Parameters": {
        "BaseUrl": "https://<org>.crm4.dynamics.com/main.aspx?appid=<AppId>&pagetype=entityrecord"
      }
    }
  ]
}
```

An die angegebene Basis-URL werden automatisch `&etn=<entity>` und `&id=<guid>` angehängt.

### EmbedChildren
*(neu, verfügbar: nur D365CE als Quellsystem)*

Bettet die Zeilen eines referenzierten **Kind-Jobs** in die Nachricht des Elterndatensatzes
ein, sodass Eltern- und Kind-Datensätze gemeinsam als eine Nachricht durch den Service Bus
laufen und gemeinsam ins Ziel geschrieben werden — z. B. eine Rechnung mit ihren
Rechnungspositionen.

```json
{
  "Operations": [
    {
      "Name": "EmbedChildren",
      "Parameters": { "JobName": "invoice-lines" }
    }
  ]
}
```

Zugehörige Zeile in der Tabelle:

| Spalte | Wert |
|---|---|
| SourceField | `_lines` (Unterstrich-Präfix, beliebiger Name) |
| TargetField | Pfad im Zieldatensatz, unter dem das Array landet (z. B. `lines`) |

Der referenzierte Kind-Job liefert `SourceConnection`, Quellressource und `Mapping`; sein
`Filter` kann `{{...%parent.feld%...}}`-Platzhalter enthalten, die zur Abrufzeit gegen den
Eltern-Quelldatensatz aufgelöst werden. Beim Schreiben in eine
[OpenAPI-Zielressource](../../konnektoren/openapi/#embedchildren--eingebettete-kind-datensätze)
wird daraus ein verschachteltes JSON-Array gebaut und unverändert — ohne Feld-für-Feld-Abgleich
gegen ein eventuell vorhandenes Zielarray — mit der Elternanfrage übertragen.

---

## TargetOperation — Transformationen beim Schreiben

TargetOperations werden angewendet, **bevor** der Wert ins Zielsystem geschrieben wird.

> **TargetOperations werden über den Namen aufgelöst**
>
> Anders als bei SourceOperations dürfen mehrere Einträge im Array stehen. Sie werden
> jedoch **nicht der Reihe nach ausgeführt**, sondern anhand ihres Namens gesucht — die
> Reihenfolge im Array hat keine Wirkung.
> 
> Es gilt eine feste Rangfolge:
> 
> 1. **`DefaultValue`** — überschreibt den Quellwert vollständig
> 2. **`DefaultValueIfNullOrEmpty`** — greift, wenn das Ergebnis leer ist
> 3. **`DefaultValueMap`** — übersetzt den bis dahin ermittelten Wert zuletzt



### DefaultValue
*(alle Konnektoren)*

Schreibt immer einen festen Wert ins Zielfeld — unabhängig vom Quellwert.
Das `SourceField` kann in diesem Fall leer gelassen werden.

```json
{
  "Operations": [
    { "Name": "DefaultValue", "Parameters": { "DefaultValue": "2" } }
  ]
}
```

### DefaultValueIfNullOrEmpty
*(alle Konnektoren)*

Schreibt einen festen Wert nur dann, wenn das Quellfeld leer oder `null` ist.
Ist ein Quellwert vorhanden, wird dieser unverändert verwendet.

```json
{
  "Operations": [
    { "Name": "DefaultValueIfNullOrEmpty", "Parameters": { "DefaultValue": "unbekannt" } }
  ]
}
```

### DefaultValueMap
*(alle Konnektoren)*

Übersetzt den ermittelten Wert anhand einer Zuordnungsliste. Der Schlüsselvergleich
erfolgt **ohne Beachtung der Groß-/Kleinschreibung**.

```json
{
  "Operations": [
    {
      "Name": "DefaultValueMap",
      "Parameters": {
        "Mapping": [
          { "Key": "DE", "Value": "Deutschland" },
          { "Key": "AT", "Value": "Österreich" }
        ]
      }
    }
  ]
}
```

Findet sich kein passender Schlüssel, bleibt der Wert **unverändert** — im Unterschied zu
`OptionMapping`, das in diesem Fall einen Fehler wirft.

### Lookup
*(nur D365CE)*

Befüllt ein Lookup-Feld in Dataverse, indem der verknüpfte Datensatz über einen
Schlüssel gesucht wird.

```json
{
  "Operations": [
    {
      "Name": "Lookup",
      "Parameters": {
        "TargetTable": "contact",
        "TargetKey": {
          "KeyType": "CustomKey",
          "KeyName": "wysa_number",
          "KeyValues": [
            { "keyName": "wysa_number", "attributeName": "contactNo", "rank": 1 }
          ]
        }
      }
    }
  ]
}
```

`TargetTable` ist **zwingend** — ohne diesen Parameter bricht die Auflösung ab.

| Parameter | Beschreibung |
|---|---|
| `TargetTable` | Entität des zu suchenden Datensatzes (Pflicht) |
| `TargetKey` | Einzelner Schlüssel, Syntax wie beim [TargetKey](#targetkey--upsert-schlüssel) |
| `TargetKeys` | Array mehrerer Schlüssel mit `KeyRank` (ab v2.1, siehe [oben](#keyrank)) |
| `DoNotCreate` | `true` verhindert das automatische Anlegen (ab v2.1, siehe unten) |
| `SetNullIfEqualsValueOfField` | Setzt das Lookup auf `null`, wenn der Wert dem angegebenen Feld der Nachricht entspricht — Schutz gegen Selbstreferenzen |
| `DefaultValue` | Fester Wert; überspringt die Auflösung vollständig |

> **DoNotCreate — automatisches Anlegen abschalten**
>
> Standardmäßig legt die DataBridge einen nicht gefundenen Ziel-Datensatz automatisch an.
> Mit `"DoNotCreate": true` unterbleibt das: Der Lookup wird nur aufgelöst, wenn der
> Datensatz bereits existiert — andernfalls bleibt das Feld leer.
> 
> Die Option wirkt nur auf diesen Fallback. Die vorgelagerte Suche über mehrere
> `TargetKeys` läuft unverändert.



### OptionMapping
*(verfügbar: D365CE; bei D365BC und D365NAV für String-Felder)*

Übersetzt Textwerte aus dem Quellsystem in numerische Optionset-Werte von Dataverse
(und umgekehrt). Die `Mapping`-Liste enthält alle bekannten Schlüssel-Wert-Paare.

```json
{
  "Operations": [
    {
      "Name": "OptionMapping",
      "Parameters": {
        "Mapping": [
          { "Key": "Blank",                       "Value": "" },
          { "Key": "Prospect",                    "Value": "383380000" },
          { "Key": "First_x002D_time_x0020_Donor","Value": "383380001" },
          { "Key": "Recurring_x0020_Donor",       "Value": "383380002" }
        ]
      }
    }
  ]
}
```

Für **MultiSelect-Optionsets** ist zusätzlich der Parameter `OptionDelimiter` anzugeben —
er trennt die Einzelwerte im Quellstring. Ohne ihn schlägt die Aufteilung fehl.

> **Sonderzeichen in Schlüsseln**
>
> In der Praxis liefert Business Central Sonderzeichen in Feldwerten kodiert: ein
> Leerzeichen als `_x0020_`, ein Bindestrich als `_x002D_`. Diese kodierten Werte sind
> dann 1:1 als `Key` einzutragen.
> 
> Die DataBridge vergleicht die Schlüssel lediglich als Zeichenketten — die Kodierung
> stammt aus dem Quellsystem, nicht aus dieser Anwendung.



---

## Datentyp-Umwandlung

Welche Quelltypen ein Zielfeld akzeptiert, hängt vom Connector und vom Zielfeld-Typ ab.
Die vollständigen Matrizen stehen auf den [Konnektor-Seiten](../../konnektoren/).

Zwei Punkte, die häufig zu Rückfragen führen:

**String zu Dezimalzahl** *(ab v2.1)* — Dezimalfelder akzeptieren jetzt auch Quellwerte
vom Typ `string`. Diese werden **immer** im Format `en-US` interpretiert, also mit Punkt
als Dezimaltrennzeichen. Ein leerer String wird zu `null`, ein nicht konvertierbarer Wert
erzeugt einen Fehler.

**Culture bei echten Zahlentypen** — kommt der Wert dagegen bereits als `decimal` oder
`money`, gilt die im Verbindungs-JSON eingestellte `language` (Vorgabe `de-DE`).

> **Diese Asymmetrie ist beabsichtigt**
>
> Derselbe Zahlenwert wird also unterschiedlich interpretiert, je nachdem, ob er als
> Zeichenkette oder als Zahl aus dem Quellsystem kommt. Bei Feldern, die als `string`
> geliefert werden, ist der Punkt als Dezimaltrennzeichen zu verwenden — unabhängig von
> der eingestellten `language`.



---

## Spezialfälle

### Feld ohne Quellwert (reiner Standardwert)

Soll ein Zielfeld immer mit einem festen Wert befüllt werden, ohne dass ein
Quellfeld existiert:

| Spalte | Wert |
|---|---|
| SourceField | *(leer)* |
| TargetField | `wysa_datasource` |
| TargetOperation | `DefaultValue` mit `"DefaultValue": "DataBridge"` |

### Wert aus der Antwortnachricht lesen (ResponseJob)

| Spalte | Wert |
|---|---|
| SourceField | `_id` |
| TargetField | `wysa_bcid` |
| SourceOperation | `ReadFromMessage` |

Siehe auch [Virtuelle Felder](#virtuelle-felder) und
[Jobs → ResponseJob](../jobs/#responsejob--rückschreiben-von-ids).

### Mehrere Operationen in einer Zeile

Ob mehrere Einträge im `Operations`-Array wirken, hängt davon ab, um welche Art von
Operation es sich handelt:

| | Verhalten bei mehreren Einträgen |
|---|---|
| **SourceOperation** | Nur der **erste** Eintrag wird ausgeführt, alle weiteren verworfen |
| **TargetOperation** | Mehrere Einträge sind zulässig; sie werden über ihren **Namen** aufgelöst, nicht über die Reihenfolge. Es gilt die Rangfolge `DefaultValue` → `DefaultValueIfNullOrEmpty` → `DefaultValueMap`. |

Eine Verkettung im Sinne von „erst A, dann B auf das Ergebnis von A" gibt es bei
SourceOperations damit nicht.
