Jobs (jobs)
Ein Job beschreibt genau eine Synchronisationsrichtung zwischen zwei Systemen: er verbindet eine Quelltabelle mit einer Zieltabelle, legt den Zeitplan fest und verweist auf das zugehörige Mapping.
Tabellenstruktur
| Spalte | Pflicht | Beschreibung |
|---|---|---|
| PartitionKey | ✅ | Muss immer job lauten. |
| RowKey | ✅ | Eindeutiger Jobname. Wird im Logging und als Referenz für ResponseJobs verwendet. |
| IsActive | ✅ | true = Job wird von GetChanges ausgeführt. false = Job wird nur indirekt (als ResponseJob) aufgerufen. |
| SourceConnection | ✅ | RowKey-Referenz auf einen Eintrag in der connections-Tabelle (Quelldaten). |
| TargetConnection | ✅ | RowKey-Referenz auf einen Eintrag in der connections-Tabelle (Zieldaten). |
| Mapping | ✅ | PartitionKey-Referenz auf eine Gruppe von Einträgen in der mappings-Tabelle. |
| Source | — | Quellressource (Tabelle, Entität, Pfad oder Aktion). Überschreibt die SourceTable der Mapping-Zeilen für diesen Job (siehe unten). |
| Target | — | Zielressource (Tabelle, Entität, Pfad oder Aktion). Überschreibt die TargetTable der Mapping-Zeilen für diesen Job (siehe unten). |
| Order | ✅ | Ganzzahl. Legt die Ausführungsreihenfolge fest — muss über alle Jobs eindeutig sein. |
| WatermarkField | — | Feldname im Quellsystem, der als Änderungsindikator genutzt wird (meistens ein Datums-/Zeitstempelfeld). Nur bei WatermarkType = date oder datetime erforderlich; bei string (Dataverse Change Tracking) bleibt das Feld leer. |
| WatermarkType | ✅ | Typ des Watermark-Wertes: date, datetime oder string (Details: Watermarks). |
| Filter | — | Filterausdruck für die Quellanfrage. Format ist systemabhängig (siehe unten). |
| Schedule | — | CRON-Ausdruck (6-stellig, NCrontab). Wenn leer, läuft der Job bei jedem Timer-Aufruf. |
| Action | — | Steuert, welche Änderungsarten gelesen werden, oder benennt eine Aktion. Leer = NewUpdate. Wirkt bei den CRUD-Werten (Delete, DeleteOnUpdate) nur bei D365CE als Quellsystem (siehe unten); ein nicht erkannter Wert schaltet den Job auf InvokeAction (siehe unten). |
| Priority | — | Standard (Vorgabe), High oder Low. Steuert die Service-Bus-Queue. |
| ResponseJob | — | RowKey eines anderen Jobs, der nach erfolgreichem Schreiben automatisch ausgeführt wird. |
| IsValid | — | Reserviert. Die Spalte kommt in bestehenden Tabellen vor, wird von der DataBridge aber nicht gelesen und hat keine Wirkung. |
Die DataBridge liest die Tabelle ausschließlich mit dem Filter PartitionKey eq 'job'.
Zeilen mit einem abweichenden PartitionKey werden ohne Fehlermeldung und ohne Warnung
ignoriert — der Job existiert dann für die DataBridge schlicht nicht.
Dasselbe gilt für die connections-Tabelle (dort connection). Nur bei mappings ist
der PartitionKey frei wählbar — er dient dort der Gruppierung.
Ausführungsreihenfolge
Das Feld Order bestimmt, in welcher Reihenfolge die fälligen Jobs bei jedem Timer-Aufruf
abgearbeitet werden. Empfehlung: BC-Jobs vor CE-Jobs ausführen, um sicherzustellen,
dass IDs aus BC schon in Dataverse existieren, bevor sie referenziert werden.
Order 10: BC → CE (Stammdaten, z. B. Kunden)
Order 20: BC → CE (Bewegungsdaten, z. B. Angebote)
Order 30: CE → BC (Rückmeldungen)
Der Order-Wert muss über alle Jobs eindeutig sein — auch über inaktive ResponseJobs
hinweg. Ein doppelter Wert führt dazu, dass die betroffenen Jobs als unvollständig
markiert und nicht ausgeführt werden.
Filter-Syntax
Der Filter bestimmt, welche Datensätze aus der Quelle abgerufen werden.
D365BC, D365NAV und SQL — Filter mit Watermark-Platzhalter
Der Filterausdruck wird als Bedingung in die Quellanfrage eingesetzt. Zwei Elemente steuern das Verhalten:
%watermark%wird zur Laufzeit durch den gespeicherten Watermark-Wert ersetzt{{ … }}klammert einen Block, der komplett entfällt, wenn die Watermark leer ist
{{lastModifiedDateTime gt %watermark%}}
Ist die Watermark leer (Erstlauf oder manuell geleert), verschwindet der gesamte Block und der Job liest den vollständigen Bestand. Genau dafür ist die Klammerung gedacht.
Es kommt nicht darauf an, dass der Watermark-Vergleich zuerst steht. Ausgewertet wird
allein die {{ }}-Klammerung: Jeder Block, der %watermark% enthält, wird bei leerer
Watermark ersatzlos entfernt. Mehrere Blöcke sind erlaubt.
Daraus folgt eine Falle beim Kombinieren von Bedingungen:
{{lastModifiedDateTime gt %watermark%}} and {{blocked eq false}}
Bei leerer Watermark entsteht daraus " and blocked eq false" — ein ungültiger
Filterausdruck, der die Abfrage gegen das Quellsystem scheitern lässt.
Richtig kombiniert wird, indem die Zusatzbedingung außerhalb der Klammer steht und
das and mit in den Watermark-Block wandert:
blocked eq false {{and lastModifiedDateTime gt %watermark%}}
| Watermark | Ergebnis |
|---|---|
| gesetzt | blocked eq false and lastModifiedDateTime gt 2026-08-01T10:00:00.000Z |
| leer | blocked eq false |
Beide Varianten sind gültig — der Erstlauf liest den vollen Bestand, gefiltert auf
blocked eq false.
D365CE / Dataverse — JSON-Operationen
Dataverse nutzt die Change Tracking API; ein OData-Filter kommt dort nicht zum Einsatz.
Stattdessen erwartet das Filter-Feld ein JSON-Objekt, das nach dem Abruf
clientseitig ausgewertet wird — passende Datensätze werden verarbeitet, alle anderen
verworfen.
{ "Operations": [ { "Name": "NotNull", "Parameters": { "SourceField": "wysa_number" } } ] }
{ "Operations": [ { "Name": "StatusCode", "Parameters": { "Value": 3 } } ] }
| Operation | Parameter | Wirkung |
|---|---|---|
Equals | SourceField, Value | Feldwert entspricht dem angegebenen Wert |
Boolean | SourceField, Value | Boolesches Feld entspricht dem angegebenen Wert |
OptionSetValue | SourceField, Value | Optionsset-Feld entspricht dem angegebenen Wert |
NotNull | SourceField | Feld ist gefüllt |
IsNull | SourceField | Feld ist leer |
StatusCode | Value | Datensatz hat den angegebenen Statuscode |
Ein nicht aufgeführter Operationsname führt zu einem Fehler
(Undefined FilterOperation). Die unter SourceField genannten Felder werden
automatisch mit abgefragt — sie müssen nicht zusätzlich im Mapping stehen.
Schedule — jobbezogener CRON-Ausdruck
Wenn ein Job seltener als der globale Timer laufen soll, kann ein eigener
CRON-Ausdruck eingetragen werden. Die DataBridge prüft anhand von LastCronRun im
Watermark, ob das Intervall bereits abgelaufen ist.
Beispiele:
| Schedule | Bedeutung |
|---|---|
0 */2 * * * * | Alle 2 Minuten |
0 */15 * * * * | Alle 15 Minuten |
0 0 * * * * | Stündlich |
0 0 6 * * * | Täglich um 06:00 Uhr |
| (leer) | Greift bei jedem Timer-Aufruf |
Der globale Timer (GetChangesTimerExpression) steht bei einer Neuinstallation auf
*/15 * * * * * — also alle 15 Sekunden. Ein Job ohne eigenen Schedule wird damit bei
jedem dieser Läufe geprüft.
Priorität
| Wert | Service-Bus-Queue | Typischer Einsatz |
|---|---|---|
High | sbqhighexport | Zeitkritische Stammdaten (Preise, Kundenstatus) |
Standard | sbqexport | Normalbetrieb (Vorgabe, auch bei leerem oder unbekanntem Wert) |
Low | sbqlowexport | Große Historien-Importe, Bulk-Daten |
Jede der drei Queues hat einen eigenen, gleichrangigen Verarbeiter. Sie laufen unabhängig und parallel — dadurch bleibt eine hochpriore Nachricht nicht hinter einem Rückstau niedrigerer Priorität stehen. Eine echte Vorrang-Reihenfolge zwischen den Queues gibt es nicht.
Der Service Bus enthält vier weitere Queues (sbqignored, sbqtransitivehigh,
sbqtransitivestandart, sbqtransitivelow). Sie sind Ziele der internen
Fehlerbehandlung — für übersprungene bzw. bei Konflikten im Zielsystem zurückgestellte
Nachrichten — und nicht als Wert für Priority gedacht. Details:
Architektur → Queue-Struktur.
Action — welche Änderungsarten gelesen werden
| Wert | Wirkung |
|---|---|
(leer) / NewUpdate | Nur Neuanlagen und Änderungen (Vorgabe, auch bei unbekanntem Wert) |
Delete | Nur Löschungen |
DeleteOnUpdate | Löschungen, die im Quellsystem als Änderung auftreten |
All | Neuanlagen, Änderungen und Löschungen in einem Job |
| ein beliebiger anderer Wert | Der Job wird als Aktions-Job (InvokeAction) behandelt — siehe unten |
Die Werte Delete, DeleteOnUpdate und All werden ausschließlich vom Dataverse-Connector
ausgewertet. Steht als Quelle ein D365BC-, D365NAV- oder SQL-System, haben sie keine
Wirkung — der Job liest dort unabhängig vom gesetzten Wert immer Neuanlagen und
Änderungen.
InvokeAction — Aktions-Jobs
(neu) Enthält Action weder einen leeren Wert noch den Namen eines bekannten
JobActionType (NewUpdate, Delete, TimeStamp, DeleteOnUpdate), wird der Job zu
einem Aktions-Job: Statt eines CRUD-Zyklus ruft er einen benannten, nicht-CRUD-Endpunkt
auf. Der Wert von Action wird dabei unverändert als Aktionsname übernommen — z. B. ein
OpenAPI-operationId oder der Name eines CE-OrganizationRequest.
Action = SendByEInvoice
Aktuell implementiert der OpenAPI-Konnektor InvokeAction
(ResourcePath aus Source/Target bzw. TargetPath + TargetTable der Mapping-Zeilen).
Details und Konfigurationsbeispiel: Konnektoren → OpenAPI → InvokeAction.
Source und Target — job-eigene Ressourcenpfade
(neu) Bislang bestimmten ausschließlich die Mapping-Zeilen (SourceTable/TargetTable),
welche Ressource ein Job liest bzw. beschreibt. Ein Job kann jetzt zusätzlich eigene Felder
Source und Target tragen, die diese Werte überschreiben:
- Sind
Source/Targetam Job leer, gilt weiterhin das bisherige Verhalten — die Mapping-Zeilen bestimmen die Ressource (mit einer einmaligen Warnung im Log, die auf die ältere Konfigurationsform hinweist). - Ist mindestens eines der beiden Felder gesetzt, wird nur dieses überschrieben; das jeweils andere fällt weiterhin auf den Mapping-Wert zurück.
- Fehlt sogar das
Mapping-Feld am Job komplett, wird ausSource/Targetein synthetisches Mapping ohneTargetKeyerzeugt (JOB-<Jobname>) — nützlich für reine Aktions-Jobs ohne Feld-Mapping. - Enthält
Source/Targeteinen Schrägstrich (pfad/ressource), wird der Teil vor dem letzten Schrägstrich als Pfad (SourcePath/TargetPath), der Teil danach als Ressourcenname (SourceTable/TargetTable) interpretiert.
Source = v2/clients
Target = v2/client_invoices/{id}/send_by_einvoice
Damit lässt sich ein und dieselbe Mapping-Definition für mehrere Ressourcen bzw. Aktionspfade wiederverwenden, statt für jede Kombination eine eigene Mapping-Gruppe anzulegen.
ResponseJob — Rückschreiben von IDs
Ein ResponseJob löst das Problem, dass Business Central beim Anlegen eines Datensatzes eine neue interne ID vergibt, die anschließend in Dataverse gespeichert werden soll.
Ablauf:
- Job A schreibt einen Datensatz von CE nach BC
- BC antwortet mit dem neu erzeugten Datensatz inkl. der neuen BC-ID
- Die DataBridge führt automatisch Job B (den ResponseJob) aus
- Job B liest die BC-ID aus der Antwort (
ReadFromMessage-Operation) und schreibt sie zurück nach CE
Konfiguration:
Job A: IsActive = true
ResponseJob = "JobB-RowKey"
Job B: IsActive = false ← wird nur durch Job A ausgelöst, nie direkt
SourceConnection = BC-Verbindung
TargetConnection = CE-Verbindung
Felder, die aus der BC-Antwort gelesen werden, werden im Mapping mit einem
Unterstrich-Präfix im SourceField markiert und die Operation ReadFromMessage
eingetragen (Details: Mappings → ReadFromMessage).