Perform ExecuteMultiple in batches and changesets from TypeScript using dataverse-ify version 2
My free tutorial course on writing Dataverse web resources has been available for over a year now, and it has had over 1000 people enrol! The course uses version 1 of dataverse-ify
, and of course over that time I've been working on version 2 which is currently available in beta.
What is Dataverse-ify?
Dataverse-ify
aims to simplify calling the Dataverse WebAPI from TypeScript inside model-driven apps, Single Page Applications (SPAs) and integration tests running inside VSCode/Node. It uses a small amount of metadata that is generated using dataverse-gen
and a set of early bound types to make it easy to interact with dataverse tables and columns using a similar API that you might be used to using if you've used the IOrganizationService
inside C#.
You can use the new version by adding @2
on the end of the node modules:
For example:
npx dataverse-auth@2 npx dataverse-gen@2 npm install dataverse-ify@2
Soon, I will be removing the beta tag and publishing it so that it will install by default. There are a few breaking changes detailed in the Upgrading readme, but I will be publishing more samples including a Single Page Application that uses dataverse-ify
even where the Xrm.WebApi
is not available.
I wanted to give you a peek at one of the features that I am really excited about in version 2 - support for ExecuteMultiple
with batch and change set support. A batch allows you to send multiple requests in a single request, and change sets allow you to send multiple requests that will be executed as a transaction. This can give your client-side code a performance boost and make it easier to perform a single changeset where if one request fails, they all will fail. Custom API requests can even be wrapped up in executeMultiple!
Imagine that you have a Command Bar button that calls a JavaScript function from a grid that needs to make an update to a column on all of the selected records, and then wait for a triggered flow to run, as indicated by the updated column being reset. The updates can be wrapped up in an ExecuteMultiple batch rather than being made by lots of Update requests.
Create the top-level function
When a command bar calls a JavaScript function it can return a Promise if there is asynchronous work being performed. In our case, we don't want the model-driven app to wait until our flows are run, so we can use Promise.resolve
on an internal function to 'fire and forget' a long-running task:
static async CreateProjectReportTrigger(entityIds: string[]): Promise<void> { // Fire and forget the internal command so it does not cause a ribbon action timeout Promise.resolve(ProjectRibbon.CreateProjectReportTriggerInternal(entityIds)); }
Create the Internal function and initialize the metadata cache
Inside the internal function, we need to first set our metadata that was created using dataverse-gen
- this provides dataverse-ify
with some of the information, it needs to work out the data types of columns that are not present in the WebApi
responses. We also create a random value to update the column that will trigger flow:
setMetadataCache(metadataCache);jj const requestCount = entityIds.length; const trigger = "trigger" + Math.random().toString();
Make the update using executeMultiple (this is not C# remember, it's TypeScript!)
This is where the magic happens - we can create an array of UpdateRequest
objects using the entitiyIds
provided to function from the Command Bar:
// Trigger the flow for each selected project (using a batch) const updates = entityIds.map((id) => { return { logicalName: "Update", target: { logicalName: dev1_projectMetadata.logicalName, dev1_projectid: id, dev1_reportgenerationstatus: trigger, } as dev1_Project, } as UpdateRequest; }); const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); await serviceClient.executeMultiple(updates);
You can see that the updates array is simply passed into executeMultiple
which then will bundle them up inside a $batch
request. If you wanted to, you can run the updates inside a transaction by simply wrapping the batch inside an array:
await serviceClient.executeMultiple([updates]);
This array could actually contain multiple change sets which each would run independently inside a transaction.
So the resulting function would be:
static async CreateProjectReportTriggerInternal(entityIds: string[]): Promise<void> { // Update a column on the selected records, to trigger a flow try { setMetadataCache(metadataCache); const requestCount = entityIds.length; const trigger = "trigger" + Math.random().toString(); // Trigger the flow for each selected project (using a batch) const updates = entityIds.map((id) => { return { logicalName: "Update", target: { logicalName: dev1_projectMetadata.logicalName, dev1_projectid: id, dev1_reportgenerationstatus: trigger, } as dev1_Project, } as UpdateRequest; }); const serviceClient = new XrmContextDataverseClient(Xrm.WebApi); await serviceClient.executeMultiple(updates); // Monitor the result const query = `<fetch aggregate="true"> <entity name="dev1_project"> <attribute name="dev1_projectid" alias="count_items" aggregate="countcolumn" /> <filter> <condition attribute="dev1_reportgenerationstatus" operator="eq" value="${trigger}" /> </filter> </entity> </fetch>`; let complete = false; do { const inProgressQuery = await serviceClient.retrieveMultiple(query, { returnRawEntities: true }); complete = inProgressQuery.entities.length === 0; if (!complete) { const inProgressCount = inProgressQuery.entities[0]["count_items"] as number; complete = inProgressCount === 0; // Report status Xrm.Utility.showProgressIndicator(`Generating Reports ${requestCount - inProgressCount}/${requestCount}`); await ProjectRibbon.sleepTimeout(2000); } } while (!complete); Xrm.Utility.closeProgressIndicator(); } catch (e) { Xrm.Utility.closeProgressIndicator(); Xrm.Navigation.openErrorDialog({ message: "Could not generate reports", details: JSON.stringify(e) }); } } static sleepTimeout(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); }
This code adds in the polling for the number of records that have yet to have the flow run and reset the dev1_reportgenerationstatus
attribute, indicating that it is completed or reports an error.
The batch request would look similar to:
--batch_1665710705198 Content-Type: application/http Content-Transfer-Encoding: binary PATCH /api/data/v9.0/dev1_projects(2361e495-1419-ed11-b83e-000d3a2ae2ee) HTTP/1.1 Content-Type: application/json {"dev1_reportgenerationstatus":"trigger0.39324146578062336","@odata.type":"Microsoft.Dynamics.CRM.dev1_project"} --batch_1665710705198 Content-Type: application/http PATCH /api/data/v9.0/dev1_projects(e8184b63-1823-ed11-b83d-000d3a39d9b6) HTTP/1.1 Content-Type: application/json {"dev1_reportgenerationstatus":"trigger0.39324146578062336","@odata.type":"Microsoft.Dynamics.CRM.dev1_project"} --batch_1665710705198--
The code obviously can be improved by adding a timeout and better reporting of errors - but this shows the general idea of using executeMultiple
using dataverse-ify
version 2.
There are lots of other improvements in version 2 - so if you've used version 1 please do give version 2 a go whilst it's in beta and report any issues inside GitHub.
In my next post on version 2, I'll show you how to call a Custom API using a batch and changeset. If you want to peek before then - take a look at the tests for version 2 - they give lots of examples of its use.
@ScottDurow
Published on:
Learn moreRelated posts
The future of Power Platform
Power Platform & M365 Dev Community Call – January 16th, 2025 – Screenshot Summary
Call Highlights SharePoint Quicklinks: Primary PnP Website: https://aka.ms/m365pnp Documentation & Guidance SharePoint Dev Videos Issues...
Power Platform – January 2025 – Screenshot Summary
Community Call Highlights Quicklinks: Power Platform Community: Power Apps Power Automate Power BI Power Virtual Agents Power Pages M36...
Power platform – Default-type backup restore to developer-type is available
The back up and restore environments feature for power platform has been extended to allow your default-type environment backups to be restore...
Designing an effective Power Platform Tenant strategy
In today's landscape where AI and digital transformation is at the forefront, leveraging tools like Microsoft's Power Platform can revol...
Microsoft 365 & Power Platform Call (Microsoft Speakers) – January 14th, 2025 – Screenshot Summary
Call Highlights SharePoint Quicklinks: Primary PnP Website: https://aka.ms/m365pnp Documentation & Guidance SharePoint Dev Videos Issues...
We need to talk about... Power Platform... Power Platform Assessment
Next in my blog series is the walkthrough of a tool that I've been using a lot with clients recently. The tool is question is the Power...
Power Platform – Tenant-to-tenant migrations – public preview announcement
Administrators will soon be able to use PowerShell for tenant-to-tenant migrations in Power Platform, enabling them to move environments betwe...