Jobs (jobs)

Die jobs-Tabelle definiert, welche Daten wann und in welche Richtung synchronisiert werden.

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

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

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.

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%}}
WatermarkErgebnis
gesetztblocked eq false and lastModifiedDateTime gt 2026-08-01T10:00:00.000Z
leerblocked 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 } } ] }
OperationParameterWirkung
EqualsSourceField, ValueFeldwert entspricht dem angegebenen Wert
BooleanSourceField, ValueBoolesches Feld entspricht dem angegebenen Wert
OptionSetValueSourceField, ValueOptionsset-Feld entspricht dem angegebenen Wert
NotNullSourceFieldFeld ist gefüllt
IsNullSourceFieldFeld ist leer
StatusCodeValueDatensatz 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:

ScheduleBedeutung
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

WertService-Bus-QueueTypischer Einsatz
HighsbqhighexportZeitkritische Stammdaten (Preise, Kundenstatus)
StandardsbqexportNormalbetrieb (Vorgabe, auch bei leerem oder unbekanntem Wert)
LowsbqlowexportGroß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.


Action — welche Änderungsarten gelesen werden

WertWirkung
(leer) / NewUpdateNur Neuanlagen und Änderungen (Vorgabe, auch bei unbekanntem Wert)
DeleteNur Löschungen
DeleteOnUpdateLöschungen, die im Quellsystem als Änderung auftreten
AllNeuanlagen, Änderungen und Löschungen in einem Job
ein beliebiger anderer WertDer Job wird als Aktions-Job (InvokeAction) behandelt — siehe unten

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/Target am 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 aus Source/Target ein synthetisches Mapping ohne TargetKey erzeugt (JOB-<Jobname>) — nützlich für reine Aktions-Jobs ohne Feld-Mapping.
  • Enthält Source/Target einen 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:

  1. Job A schreibt einen Datensatz von CE nach BC
  2. BC antwortet mit dem neu erzeugten Datensatz inkl. der neuen BC-ID
  3. Die DataBridge führt automatisch Job B (den ResponseJob) aus
  4. 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).