L’endpoint Web API e il modello OData v4

Dataverse espone i propri dati e la propria logica tramite una Web API RESTful conforme a OData v4, raggiungibile all’indirizzo https://<org>.crm.dynamics.com/api/data/v9.2/. È il canale di integrazione preferito per client esterni perché è puramente HTTP + JSON, non richiede assembly proprietari e funziona da qualsiasi linguaggio. L’autenticazione avviene tramite Microsoft Entra ID con OAuth 2.0: si registra un’app in Entra, si concede il permesso delegato o applicativo su Dataverse e si acquisisce un bearer token.

I set di entità usano il nome plurale logico (accounts, contacts, new_projects) e ogni riga è identificata dalla primary key GUID nell’URL: /accounts(<guid>).

Operazioni CRUD

  • Create: POST /accounts con il corpo JSON. La risposta di default è 204 No Content con l’header OData-EntityId che contiene l’URI del record creato.
  • Retrieve: GET, con le query option OData $select, $filter, $orderby, $top, $expand per navigare le relazioni. Usa sempre $select per limitare le colonne e ridurre payload e consumo di risorse.
  • Update: PATCH /accounts(<guid>) — aggiornamento parziale, solo i campi inviati. Un PATCH su un record inesistente può eseguire un upsert.
  • Delete: DELETE /accounts(<guid>).

Per associare record correlati si usa l’annotazione @odata.bind: "primarycontactid@odata.bind": "/contacts(<guid>)".

L’header Prefer per il controllo della risposta

  • Prefer: return=representation fa restituire il record completo aggiornato subito dopo un POST/PATCH (risposta 201/200 con il body), evitando una GET successiva.
  • Prefer: odata.include-annotations="*" include le annotazioni di formattazione, ad esempio le etichette dei choice/lookup (@OData.Community.Display.V1.FormattedValue).
  • Prefer: odata.maxpagesize=<n> imposta la dimensione di pagina.

$batch, function e action

POST /$batch raggruppa più operazioni in una singola richiesta HTTP multipart. Le operazioni di modifica dentro un changeset sono transazionali (atomiche: o tutte o nessuna), mentre le GET vanno fuori dal changeset. È il meccanismo giusto per bulk load e per rispettare i limiti di throughput.

Dataverse espone anche logica applicativa:

  • Function (GET): operazioni di sola lettura senza side-effect, es. WhoAmI(), RetrieveTotalRecordCount.
  • Action (POST): operazioni con side-effect, es. Merge, WinOpportunity, oppure custom API/custom action definite dal maker.

Web API vs Organization Service SDK

L’Organization Service (SDK .NET, IOrganizationService, ServiceClient del pacchetto Microsoft.PowerPlatform.Dataverse.Client) usa un contratto a messaggi (Create, Retrieve, Execute, QueryExpression/FetchXML). Scegli l’SDK quando:

  • l’integrazione è in .NET e vuoi tipizzazione forte (early-bound), gestione robusta di paging/retry già incapsulata e riuso di plugin/logica server-side;
  • servono query complesse con FetchXML (aggregazioni, link-entity avanzati).

Scegli la Web API quando il client non è .NET (JavaScript, Python, Java), quando integri con logic app/servizi HTTP, o dentro Power Apps/Power Automate. Concettualmente entrambe colpiscono lo stesso motore e gli stessi service protection limit; cambia solo il contratto (REST/OData vs SOAP/messaggi).

Trappole tipiche d’esame

  • Service protection limit → 429: un’integrazione che invia troppe richieste in poco tempo (o supera i limiti per utente/connessione) riceve HTTP 429 Too Many Requests con header Retry-After. La risposta corretta è implementare retry con backoff rispettando Retry-After, non aumentare i thread né ignorare l’errore.
  • Paging su grandi result set: quando i risultati eccedono la pagina, la risposta contiene @odata.nextLink. Devi seguire quel link così com’è (contiene il paging cookie); NON ricostruire l’URL con $skip manuale. Per fissare la dimensione usa Prefer: odata.maxpagesize.
  • Alternate key negli URL: per referenziare un record tramite chiave alternativa (es. codice esterno) l’URL corretto è /accounts(accountnumber='ACC-100'), non /accounts?$filter=.... Richiede che una alternate key sia definita sulla tabella; con chiave composta si passano tutte le colonne.
  • return=representation: se lo scenario chiede di ottenere il record appena creato/aggiornato in una sola chiamata, la risposta è l’header Prefer: return=representation, non una seconda GET.
  • Transazionalità in $batch: per rendere atomiche più modifiche vanno racchiuse in un changeset dentro $batch; operazioni fuori dal changeset non sono transazionali.