# D365CE — Dataverse / Customer Engagement

> Konnektor für Microsoft Dataverse und Dynamics 365 Customer Engagement. Unterstützt Lesen und Schreiben.

---

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

---

Der D365CE-Konnektor verbindet die DataBridge mit **Microsoft Dataverse** und
**Dynamics 365 Customer Engagement** über das offizielle Dataverse SDK.
Er nutzt die Change Tracking API für eine lückenlose Erkennung aller Änderungen.

| Eigenschaft | Wert |
|---|---|
| Lesen | ✅ unterstützt |
| Schreiben | ✅ unterstützt |
| Job-Filter | ✅ clientseitig nach dem Abruf |
| Status | Freigegeben |
| Getestete Mindestversion | Dataverse / D365CE v9.1 |

Die Mindestversion beschreibt den getesteten Stand; eine Versionsprüfung findet im Code
nicht statt.

---

## Voraussetzung: Change Tracking aktivieren

Für alle Dataverse-Tabellen, die als **Quelle** genutzt werden, muss Change Tracking
aktiviert sein. Ohne Change Tracking kann der Konnektor keine Differenzabfragen
durchführen.

**Aktivierung:** Power Platform Admin Center → Umgebung → Tabellen → Tabelle auswählen
→ Eigenschaften → Änderungsnachverfolgung aktivieren ✅

---

## Verbindungsparameter

```json
{
  "connectionstring": "<XRM-Connection-String>"
}
```

Beispiel (Client Secret Authentication):

```
AuthType=ClientSecret;Url=https://<org>.crm4.dynamics.com;ClientId=<id>;ClientSecret=<secret>
```

### Alternative: Managed Identity (ab v2.3)

Statt eines Connection-Strings mit Secret kann die Verbindung über eine Managed Identity
aufgebaut werden:

```json
{
  "url": "https://<org>.crm4.dynamics.com",
  "clientid": "<Client-ID der user-assigned Identity, optional>"
}
```

- Ohne `clientid` wird die **system-zugewiesene** Identity der Function App verwendet, mit `clientid` die **benutzer-zugewiesene**.
- Ist `connectionstring` gesetzt, hat er **immer Vorrang**; `url` wird dann ignoriert.
- Sind weder `connectionstring` noch `url` gesetzt, bricht der Verbindungsaufbau mit einem Fehler ab.
- Voraussetzung: Die Identity ist in der Dataverse-Umgebung als **Application User** angelegt und hat eine passende Sicherheitsrolle (Power Platform Admin Center → Umgebung → Einstellungen → Benutzer + Berechtigungen → Anwendungsbenutzer).

Die vollständige XRM-Connection-String-Referenz findet sich in der
[Microsoft-Dokumentation](https://learn.microsoft.com/en-us/power-apps/developer/data-platform/xrm-tooling/use-connection-strings-xrm-tooling-connect).

Zusätzlich stehen die universellen Parameter `keyVaultUrl`, `maxPageSize`, `language` und
`httpClientTimeoutSeconds` zur Verfügung — siehe
[Verbindungen](../../konfiguration/verbindungen/).

---

## Tabellen- und Feldnamen

### SourceTable / TargetTable

Logischer Entitätsname in Kleinbuchstaben — so wie er in den Dataverse-Metadaten steht.

| Entität | SourceTable |
|---|---|
| Account | `account` |
| Contact | `contact` |
| Custom Entity | `wysa_bcsegment` |

### SourceField / TargetField

Logischer Attributname in Kleinbuchstaben.

| Attribut | SourceField |
|---|---|
| Account Name | `name` |
| Custom Field | `wysa_bcsystemid` |

---

## Unterstützte TargetKey-Typen

| Typ | Beschreibung |
|---|---|
| `PrimaryKey` | Dataverse GUID (Primärschlüssel der Entität) |
| `CustomKey` | Benutzerdefinierter Schlüssel über ein oder mehrere Felder (Alternate Key) |
| `Filter` | Suche über Feldwert-Kombination ohne expliziten Schlüssel |

Bei `CustomKey` werden Schlüsselteile vom Typ `int32`, `guid` und `string` unterstützt;
jeder andere Typ führt zum Abbruch (`keytype … not yet supported for key …`).
Leere Schlüsselteile werden übersprungen — bleibt danach kein Teil übrig, gilt der
Schlüssel als nicht auflösbar.

Ist der Alternativschlüssel in Dataverse nicht angelegt oder nicht aktiv, bricht die Suche
mit einem Fehler ab (ab v2.3). Die Nachricht landet nach den Wiederholungen in der
Dead-Letter-Queue und wird **nicht** als neuer Datensatz angelegt. Bis v2.2 wurde dieser
Fall wie „Datensatz nicht gefunden“ behandelt, was zu Dubletten führen konnte.

Ein `KeyValue` kann statt `attributeName` ein `StaticValue` enthalten; der Schlüsselteil
wird dann fest gesetzt und als Typ `string` behandelt.

> **Der Typ `Id` existiert im CE-Konnektor nicht**
>
> Anders als bei [D365BC](../d365bc/) und [D365NAV](../d365nav/) kennt der CE-Konnektor
> keinen TargetKey-Typ `Id`. Ein unbekannter oder falsch geschriebener `KeyType` löst
> **keinen Fehler** aus, sondern nur eine Warnung im Log
> (`Undefined Key definition for InitiateEntityWithKey`); der Schlüssel gilt anschließend
> als nicht auflösbar.
> 
> Für die Fehlersuche bedeutet das: Ein Lookup, das dauerhaft leer bleibt, oder ein Upsert,
> der scheinbar wirkungslos ist, kann schlicht an einem ungültigen `KeyType` liegen. Der
> Hinweis erscheint ausschließlich als Warnung, nicht als Ausnahme.



### Mehrere Ziel-Schlüssel mit Rangfolge (ab v2.1)

Statt eines einzelnen `TargetKey` lässt sich eine Liste `TargetKeys` angeben. Die
Schlüssel werden nach `KeyRank` **aufsteigend** geprüft; der erste Schlüssel, der im
Zielsystem einen Datensatz findet, gewinnt — alle weiteren werden übersprungen.

```json
{
  "Operations": [
    {
      "Name": "Lookup",
      "Parameters": {
        "TargetTable": "wysa_countryregion",
        "TargetKeys": [
          {
            "KeyType": "CustomKey",
            "KeyRank": 1,
            "KeyName": "wysa_externalid",
            "KeyValues": [
              { "keyName": "wysa_externalid", "attributeName": "externalId", "rank": 1 }
            ]
          },
          {
            "KeyType": "CustomKey",
            "KeyRank": 2,
            "KeyName": "wysa_code",
            "KeyValues": [
              { "keyName": "wysa_code", "attributeName": "countryRegionCode", "rank": 1 }
            ]
          }
        ]
      }
    }
  ]
}
```

Bestehende Mappings mit einem einzelnen `TargetKey` funktionieren unverändert weiter.

---

## SourceOperations

### LookupValue

Liest einen Wert aus einem verknüpften Dataverse-Datensatz nach.

```json
{
  "Operations": [
    {
      "Name": "LookupValue",
      "Parameters": {
        "LookupType": "Attribute",
        "SourceField": "wysa_countrycode",
        "LookupTable": "wysa_country",
        "LookupField": "wysa_name",
        "UseCache": true
      }
    }
  ]
}
```

`LookupType` kennt zwei Werte. **Nur der CE-Konnektor unterstützt beide:**

| Wert | Wirkung |
|---|---|
| `Attribute` | Der genannte `LookupField`-Wert wird aus dem verknüpften Datensatz gelesen |
| `Name` | Der Anzeigename der Referenz wird direkt aus dem Quelldatensatz übernommen — ohne zusätzliche Abfrage |

> **Vorab-Zwischenspeicherung von Lookups (ab v2.1)**
>
> Der CE-Konnektor löst benötigte Lookup-Werte vor der Verarbeitung eines Batches gebündelt
> auf und legt sie im Cache ab. Die IDs werden dazu in Blöcken zu je 1.000 Stück abgefragt,
> statt jeden Wert einzeln nachzuschlagen. Das beschleunigt die Verarbeitung großer
> Änderungsmengen spürbar.
> 
> Die Vorab-Auflösung greift **nur bei `UseCache: true`**. Ohne diese Option bleibt es bei
> Einzelabfragen. Die Funktion ist dem CE-Konnektor vorbehalten — D365BC und D365NAV
> verfügen nicht darüber.



### ReadFromMessage

Liest Werte aus einer vorherigen Quellanfrage — **nur in ResponseJobs**.
Verhalten wie beim [D365BC-Konnektor](../d365bc/).

### ActivityParty

Liest ein Feld über eine ActivityParty-Beziehung. Wird für CE-Aktivitäten
(E-Mails, Aufgaben, Anrufe) 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"
        }
      }
    }
  ]
}
```

`TypeMask` entspricht dem Dataverse-Feld `participationtypemask` und wird von der
DataBridge unverändert an die Abfrage durchgereicht. Die gültigen Werte gibt die
Dataverse-Plattform vor; die gebräuchlichsten sind:

| TypeMask | Teilnehmerrolle |
|---|---|
| `1` | From (Absender) |
| `2` | To (Empfänger) |
| `3` | CC |
| `4` | BCC |

Diese Tabelle ist eine **Plattform-Referenz**, keine Festlegung der DataBridge. Die
verbindliche und vollständige Liste steht in der Microsoft-Dokumentation zur Entität
`activityparty`.

### LinkToRecord

Erzeugt eine anklickbare URL zu einem Datensatz in Dataverse.

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

---

## TargetOperations

### Lookup

Befüllt ein Lookup-Feld in Dataverse, indem der verknüpfte Datensatz über eine
Feldwert-Kombination gesucht wird. Die Operation gehört in die Spalte `TargetOperation`
des Mappings und benötigt zwingend den `Operations`-Rahmen, den Namen `Lookup` sowie
`TargetTable`.

```json
{
  "Operations": [
    {
      "Name": "Lookup",
      "Parameters": {
        "TargetTable": "wysa_countryregion",
        "TargetKey": {
          "KeyType": "CustomKey",
          "KeyName": "wysa_code",
          "KeyValues": [
            { "keyName": "wysa_code", "attributeName": "countryRegionCode", "rank": 1 }
          ]
        }
      }
    }
  ]
}
```

> **TargetTable ist Pflicht**
>
> Ohne `TargetTable` ist nicht bestimmt, in welcher Entität gesucht werden soll — die
> Operation schlägt fehl. Fehlt der `Operations`-Rahmen oder der `Name`, wird die Operation
> gar nicht erst erkannt und die Verarbeitung bricht mit `Lookup Operation missing` ab.



Wird kein passender Datensatz gefunden, legt die DataBridge standardmäßig einen neuen an.

### Weitere Parameter der TargetOperation

| Parameter | Wirkung |
|---|---|
| `TargetTable` | Entität, in der gesucht wird. Bei `Lookup` Pflicht. |
| `TargetKey` / `TargetKeys` | Schlüssel bzw. Schlüsselliste zur Suche (siehe oben) |
| `DefaultValue` | Fester Wert, der ohne Suche gesetzt wird. Bei Lookup-Feldern eine GUID; bei Options-Feldern der numerische Optionswert. |
| `SetNullIfEqualsValueOfField` | Name des Primärschlüsselfeldes. Stimmt der aufzulösende Wert mit dem Schlüssel des Quelldatensatzes überein, wird `null` gesetzt statt eine Selbstreferenz zu erzeugen. |
| `OptionDelimiter` | Trennzeichen, mit dem ein Quellwert für ein MultiSelect-Feld in Einzelwerte zerlegt wird |
| `DoNotCreate` | `true` verhindert die automatische Neuanlage (siehe unten) |

### DoNotCreate (ab v2.1)

Ist `DoNotCreate` auf `true` gesetzt, wird der Zieldatensatz eines Lookups nur dann
verknüpft, wenn er bereits existiert. Wird er nicht gefunden, bleibt das Feld leer —
es entsteht **kein** neuer Datensatz.

```json
{
  "Operations": [
    {
      "Name": "Lookup",
      "Parameters": {
        "TargetTable": "wysa_countryregion",
        "DoNotCreate": true,
        "TargetKey": {
          "KeyType": "CustomKey",
          "KeyName": "wysa_code",
          "KeyValues": [
            { "keyName": "wysa_code", "attributeName": "countryRegionCode", "rank": 1 }
          ]
        }
      }
    }
  ]
}
```

### OptionMapping

Übersetzt Quellwerte in numerische Dataverse-Optionset-Werte.

```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" }
        ]
      }
    }
  ]
}
```

Ein leerer `Value` setzt das Zielfeld auf `null`. Ein Quellwert, der in der Liste nicht
vorkommt, führt zum Abbruch (`Could not find TableMapping for …`).

> **Kodierung von Sonderzeichen — Praxisbeobachtung**
>
> In der Praxis liefert Business Central Sonderzeichen in Optionswerten kodiert:
> Leerzeichen als `_x0020_`, Bindestrich als `_x002D_`. Diese kodierten Werte sind als `Key`
> exakt so einzutragen, wie sie von BC geliefert werden.
> 
> Es handelt sich um eine Beobachtung aus laufenden Installationen, nicht um eine
> Umwandlung, die die DataBridge selbst vornimmt — der Vergleich erfolgt zeichengenau
> (Groß-/Kleinschreibung wird ignoriert). Im Zweifel den tatsächlich gelieferten Wert über
> `logReceivedData` an der Quellverbindung prüfen.



---

## Datentypen — Zieltyp und akzeptierte Quelltypen

Beim Schreiben bestimmt der Attributtyp des **Zielfeldes** aus den Dataverse-Metadaten,
welche Umwandlung angewendet wird.

| Zielattribut (Dataverse) | Akzeptierte Quelltypen | Verhalten und Sonderfälle |
|---|---|---|
| `BigIntAttributeMetadata` | `int64`, `int32` | Leerwert → `null` |
| `BooleanAttributeMetadata` | `boolean`, `string`, `byte` *(ab v2.3)*, `int32` *(ab v2.3)* | Leerwert → `null`; bei Ganzzahlen `0` → Nein, sonst Ja |
| `DateTimeAttributeMetadata` | `date`, `datetime`, `string` | Leerwert → `null`; auch `0001-01-01` wird zu `null` |
| `DecimalAttributeMetadata` | `decimal`, `money` | Interpretation mit der Kultur aus `language` |
| `DecimalAttributeMetadata` | `string` *(ab v2.1)* | Interpretation **immer** als `en-US`, unabhängig von `language` |
| `DoubleAttributeMetadata` | `double`, `decimal` | Interpretation mit der Kultur aus `language`. **`string` wird nicht akzeptiert.** |
| `EntityNameAttributeMetadata` | alle Quelltypen | Wird als Text behandelt; Leerwert → `null` |
| `IntegerAttributeMetadata` | `int32`, `optionsetvalue`, `string` | Leerwert → `null` |
| `LookupAttributeMetadata` | typunabhängig | Erfordert eine `Lookup`- oder `DefaultValue`-TargetOperation, sonst `Lookup Operation missing` |
| `MemoAttributeMetadata` | alle Quelltypen | Wird als Text behandelt; Leerwert → `null` |
| `MoneyAttributeMetadata` | **nur `decimal`** | `money`, `double` und `string` führen zum Abbruch |
| `MultiSelectPicklistAttributeMetadata` | **nur `string`** | Erfordert `OptionMapping`; Zerlegung über `OptionDelimiter`. Leerwert → `null` |
| `PicklistAttributeMetadata` | `string`, `int32` | Erfordert `OptionMapping` oder `DefaultValue` |
| `StateAttributeMetadata` | `boolean`, `string`, `int32`, `byte` *(ab v2.3)* | Erfordert `OptionMapping` oder `DefaultValue` |
| `StatusAttributeMetadata` | `string`, `int32` | Erfordert `OptionMapping` oder `DefaultValue` |
| `StringAttributeMetadata` | alle Quelltypen | Wert wird getrimmt; Leerwert → `null` |

Sonderfälle jenseits der Tabelle:

- `UniqueIdentifierAttributeMetadata` sowie Primärschlüssel- und logische Attribute
  (z. B. `statuscodename`) werden beim Schreiben **stillschweigend übersprungen**
- `FileAttributeMetadata` und `ImageAttributeMetadata` werden **nicht unterstützt**
  (`type not supported`)
- Jeder sonstige, nicht aufgeführte Attributtyp führt zum Abbruch
  (`Type … not yet implemented`)

> **Die Zahlentypen verhalten sich bewusst unterschiedlich**
>
> Die drei numerischen Zieltypen akzeptieren **nicht** dieselben Quelltypen:
> 
> - `Decimal` akzeptiert seit v2.1 auch `string` — dann aber **immer** im Format `en-US`.
>   Ein deutsch formatierter Text wie `1.234,56` wird dabei falsch oder gar nicht gelesen.
> - `Money` akzeptiert **ausschließlich** `decimal`. Ein Quellfeld, das als `money` oder
>   `double` typisiert ankommt, führt zum Abbruch — auch wenn der Wert inhaltlich passt.
> - `Double` akzeptiert keinen `string`.
> 
> Zeigt ein Mapping `Source Type not yet implemented`, liegt in aller Regel eine dieser drei
> Abweichungen vor.



---

## Sonderentitäten

Einige Dataverse-Entitäten werden nicht über den generischen Weg verarbeitet.

### listmember — Marketinglisten

Datensätze der Entität `listmember` werden **nicht** über den generischen Schreibpfad
angelegt oder gelöscht, sondern über die Dataverse-Nachrichten `AddMemberList` bzw.
`RemoveMemberList`. Das Mapping muss daher die Felder `listid` und `entityid` befüllen —
sie liefern die Kennungen für die beiden Nachrichten.

### Entitäten ohne statuscode

Für die folgenden Entitäten wird das Attribut `statuscode` beim Lesen automatisch aus der
Spaltenliste entfernt, weil sie es nicht besitzen:

| Entität |
|---|
| `listmember` |
| `quotedetail` |
| `opportunityproduct` |
| `salesorderdetail` |

Ein Job-Filter mit der Operation `StatusCode` ist für diese Entitäten folglich nicht
verwendbar.

---

## Job-Filter

Der CE-Konnektor wertet das Feld `Filter` des Jobs **clientseitig** aus: Die Datensätze
werden über die Change Tracking API abgerufen und anschließend geprüft; nicht passende
Datensätze werden verworfen.

Unterstützt werden die Operationen `Equals`, `Boolean`, `OptionSetValue`, `NotNull`,
`IsNull` und `StatusCode`. Ein anderer Operationsname führt zum Abbruch
(`Undefined FilterOperation`).

Syntax, Parameter und Beispiele sind unter
[Jobs → Filter-Syntax](../../konfiguration/jobs/) beschrieben.

---

## Änderungserkennung (Watermark)

| Eigenschaft | Wert |
|---|---|
| Empfohlener WatermarkType | `string` |
| WatermarkField | beliebig (wird von der Change Tracking API intern verwaltet) |

Der CE-Konnektor nutzt die **Dataverse Change Tracking API**. Diese liefert bei jedem
Abruf einen opaken Token, der beim nächsten Aufruf übergeben wird. So werden alle
Änderungen erfasst — auch wenn der Timer genau während einer Änderung ausgeführt wird.
