Data Manipulation https://dynamics-chronicles.com/ en Dataverse: Duplicate Detection Rules vs Alternate Keys https://dynamics-chronicles.com/article/dataverse-duplicate-detection-rules-vs-alternate-keys <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Dataverse: Duplicate Detection Rules vs Alternate Keys</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/stephane-pelhatre" lang="" about="/user/stephane-pelhatre" typeof="schema:Person" property="schema:name" datatype="" class="username">Stephane Pelhatre</a></span> <span property="schema:dateCreated" content="2025-10-08T15:57:17+00:00" class="field field--name-created field--type-created field--label-hidden">Wed, 10/08/2025 - 17:57</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><h3>Introduction</h3> <p>Having clean data in Dataverse can be tricky. One of the challenges is detecting and managing duplicate records.<br /> With out-of-the-box tools you can implement duplicate detection rules or create alternate keys.<br /> If you are a developer, you can implement a custom validation in a plugin.<br /> In this article we will focus on out-of-the-box features (duplicate detection rules and alternate keys) and compare them.</p> <h3>Duplicate detection rules</h3> <p>First step is to check if the feature <em>Duplicate Detection</em> is enabled in your environment (it is enabled by default):</p> <ol> <li>Connect to Dynamics 365 and open <strong>Power Platform Environment Settings </strong>app</li> <li>Click on <strong>Data Management</strong> on the left pane and select <strong>Duplicate detection settings<br /> <img alt="Enable1" data-entity-type="file" data-entity-uuid="82de77bd-935c-4901-8e51-323c559ac4e7" src="/sites/default/files/inline-images/enable%201_2.png" /></strong><br />  </li> <li>Check that the option <strong>Enable duplicate detection</strong> is enabled<br /> <img alt="Enable2" data-entity-type="file" data-entity-uuid="29cd6636-f3cb-4b3a-8a4b-113f4c280994" src="/sites/default/files/inline-images/enable%202_1.png" /><br /> <br /> There are 3 triggers that you can activate:</li> </ol> <ul> <li>When a record is created or updated</li> <li>When Microsoft Dynamics 365 for Outlook goes from offline to online</li> <li>During data import</li> </ul> <p>Second step is to make sure the feature is enabled for your table:</p> <ol> <li>Open <a href="https://make.powerapps.com/">Power Apps Portal</a> and select your environment</li> <li>Select your table on the left pane and click on <strong>Properties<br /> <img alt="Enable3" data-entity-type="file" data-entity-uuid="b063d228-a864-4888-984c-3c98f81a79f1" src="/sites/default/files/inline-images/enable%203.png" /></strong><br />  </li> <li>Expand <strong>Advanced options</strong> pane and ensure that <strong>Apply duplicate detection rules</strong> is checked<br /> <img alt="Enable4" data-entity-type="file" data-entity-uuid="3a0e8ef4-f0d5-488f-b539-5a63673485d7" src="/sites/default/files/inline-images/enable%204_0.png" /></li> </ol> <p>Then you can create duplicate detection rules:</p> <ol> <li>Connect to Dynamics 365 and open<strong> Power Platform Environment Settings </strong>app</li> <li>Click on <strong>Data Management</strong> on the left pane and select <strong>Duplicate detection rules<br /> <img alt="rule1" data-entity-type="file" data-entity-uuid="39c290ff-cd34-416d-8e94-472057d321ed" src="/sites/default/files/inline-images/rule1_0.png" /></strong><br />  </li> <li>All out-of-the-box duplicate detection rules are displayed<br /> <img alt="rule2" data-entity-type="file" data-entity-uuid="ebc76ed5-f1b1-4038-9fdf-ef7aef026f24" src="/sites/default/files/inline-images/rule2_1.png" /><br />  </li> <li>Click on <strong>New </strong>on the command bar<br /> Below an example with the out-of-the-box rule <em>Contacts with the same first name and last name </em>for Contact table<br /> <img alt="rule3" data-entity-type="file" data-entity-uuid="99d8e059-d2be-45da-b97b-3e4c57da25f4" src="/sites/default/files/inline-images/rule3_0.png" /><br /> <br /> Provide a name and description for the rule and define the criteria for identifying duplicates:</li> </ol> <ul> <li><strong>Base Record Type</strong>: select the type of record the rule applies to (e.g., Contacts).</li> <li><strong>Matching Record Type</strong>: select the type of record to compare. Typically, this is the same as the base record type, but you can compare different types (e.g., Contacts and Leads).</li> <li><strong>Exclude Inactive Records</strong>: check this box if you want to exclude inactive records from the duplicate detection process.</li> <li><strong>Case-Sensitive</strong>: check this box if the rule should be case-sensitive.</li> <li><strong>Field Selection</strong>: for each criterion, select the fields to compare and the operator (e.g., Exact Match).</li> <li>I<strong>gnore Blank Values</strong>: check this box if you do not want the rule to consider blank fields as equal.<br type="_moz" /> <br /> You can add several criterions to your rule criteria:<br /> For text fields you have 3 choices:<br /> - <em>Exact Match</em><br /> - <em>Same First Characters</em><br /> - <em>Same Last Characters</em><br /> For Datetime fields you have 2 choices:<br /> - <em>Same date</em><br /> - <em>Same date and time</em><br /> For whole number/decimal and lookup fields only <em>Exact Match</em> is available</li> </ul> <p>      5. After configuring the criteria, click on <strong>Save</strong>.<br />           To make the rule active, select it and click on <strong>Publish</strong>.<br />           Note that you can only publish up to five rules for the same base record type at a time.</p> <p>Now let's try creating a duplicate record using Dynamics 365 UI. For example a contact with existing first name and last name.<br /> You should see a pop-up dialog with the warning message <em>Duplicate records found</em>.<br /> <img alt="rule4" data-entity-type="file" data-entity-uuid="2b2bc889-8f1c-4e1a-b9eb-c9475edd7e12" src="/sites/default/files/inline-images/rule4_0.png" /></p> <p>You have 3 possibilities:</p> <ul> <li><strong>Ignore and save</strong> : if you are certain the record is unique or requires a new entry despite the match, click "Ignore and Save" to create the duplicate</li> <li><strong>Merge </strong>: if you want to combine the information from both records into a single, primary record, select "Merge." You will typically be prompted to choose which record will be the primary one, and then you can select which fields to keep from each record</li> <li><strong>Cancel </strong>: do not create the duplicate record</li> </ul> <h3>Alternate keys</h3> <p>An alternative to duplicate detection rule is to create alternate keys.<br /> This method is the same as creating unique-index(es) from the database perspective. Hence this method will be more strict compared to duplicate detection rules.<br /> An alternate key can combine one or several columns.<br /> It can contain multiple rows with NULL values. NULL values are not indexed.</p> <p>Some limitations:</p> <ul> <li>There can be a maximum of 10 alternate keys for a table in a Dataverse instance</li> <li>Columns used in the key must not have field-level security applied</li> <li>Alternate keys aren't supported in virtual tables</li> <li>When a key is created, the system validates the key, including that the total key size doesn't violate SQL-based index constraints like 900 bytes per key and 16 columns per key. If the key size doesn't meet the constraints, an error message is displayed</li> <li>Unicode characters in key value. If the data within a column that is used in an alternate key contains one of the following characters /,&lt;,&gt;,*,%,&amp;,:,\\,?,+ then retrieve (GET), update or upsert (PATCH) actions won't work. If you only need uniqueness, then this approach works, but if you need to use these keys as part of data integration then it's best to create the key on columns that won't have data with those characters</li> </ul> <p>To create an alternate key for a specific table:</p> <ol> <li>Open <a href="https://make.powerapps.com/">Power Apps Portal</a> and select your environment</li> <li>Select the table into which you want to create an alternate key</li> <li>Click on <strong>Keys</strong></li> <li>Provide a display name and select the columns to include in your key<br /> In the example below I create an alternate key in table <em>Contact </em>for columns birthday and last name<img alt="key2" data-entity-type="file" data-entity-uuid="420b5f90-3b7b-492f-b551-2310c5edbee1" src="/sites/default/files/inline-images/key2_4.png" /> <p>Only columns of the following types can be included in alternate key table definitions:<br />     - Decimal Number<br />     - Whole Number<br />     - Single line of text<br />     - Date Time<br />     - Lookup<br />     - Option Set</p> </li> <li> <p>Click on <strong>Save</strong>. The system will automatically create the index in the database.<br /> If the system detects duplicate data in the table, the creation will fail and you need to delete data first.<br /> If there are lots of existing records in your table, index creation can be a lengthy process.<br /> During creation of the key status will be <em>Pending</em>. Once the creation is complete status changes to <em>Active</em>.<br /> <img alt="key3" data-entity-type="file" data-entity-uuid="0febdf8e-6580-49ef-ad78-428a12defaf2" src="/sites/default/files/inline-images/key3_2.png" /></p> </li> </ol> <p>Now let's try creating a duplicate record using Dynamics 365 UI. To be consistent with the alternate key created previously, a contact with existing birthday and last name.<br /> You should see a pop-up error with the message <em>Duplicate record</em>.</p> <p><img alt="Key 4" data-entity-type="file" data-entity-uuid="efbb6b10-e151-47cf-801c-7fb645d4e9f4" src="/sites/default/files/inline-images/key4_0.png" /></p> <p>Therefore, it is impossible to create a duplicate record with the user interface. This makes sense as uniqueness is ensured at the database level.</p> <h3>Creating/Updating records programmatically</h3> <p>We have seen that behavior is different when we create a duplicate record with the UI using duplicate detection rules or alternate keys.<br /> But what happens if we create duplicate records programmatically with Dynamics 365 SDK or Web API?</p> <h4>Duplicate detection rules</h4> <p>By default duplicate detection rules are not applied when you are creating or updating records using Web API or Dynamics 365 API. Therefore duplicate records will be created.<br /> If you want Dynamics 365 to raise an error you must utilize an optional parameter.<br /> With Web API use parameter <code>MSCRM.SuppressDuplicateDetection</code> in the header of your HTTP request (<em>POST </em>to create a record or <em>PATCH </em>to update a record)  to avoid creation of a duplicate record of an existing record.<br /> The value assigned to <code>MSCRM.SuppressDuplicateDetection</code> determines whether the Create or Update operation can be completed:</p> <ul> <li><em>true</em>: Create or update the record, if a duplicate is found.</li> <li><em>false</em>:  Do not create or update the record, if a duplicate is found.</li> </ul> <p>So ensure that <code>MSCRM.SuppressDuplicateDetection</code> is set to <em>false </em>in your Web API request to detect duplicates.<br /> You will get the following error message if you try to create a duplicate record:</p> <p><em>{<br />   "error": {<br />     "code": "0x80040333",<br />     "message": "A record was not created or updated because a duplicate of the current record already exists."<br />   }<br /> }</em></p> <p>Note that if you do not use <code>MSCRM.SuppressDuplicateDetection </code>duplicates will be created.<br /> Below an example of POST request to create a contact named "John Doe" with activation of duplicate detection rules.</p> <pre> <code class="language-http">POST [Organization URI]/org1/api/data/v9.2/contacts If-None-Match: null OData-Version: 4.0 OData-MaxVersion: 4.0 Content-Type: application/json Accept: application/json MSCRM.SuppressDuplicateDetection: false { "firstname":"John", "lastname":"Doe" }</code></pre> <p>And below an example performed with XrmToolbox plugin <em>WebAPI Launcher.</em><br /> Note parameter <code>MSCRM.SuppressDuplicateDetection </code>in the header.</p> <p><img alt="Prog 1" data-entity-type="file" data-entity-uuid="a80f712c-01c1-45fe-9ce2-42ea21edc56f" src="/sites/default/files/inline-images/Prog1.png" /></p> <p>And the result of this Web API request if we try to create a duplicate:<br /> <img alt="Prog 2" data-entity-type="file" data-entity-uuid="49f87afd-9a4c-408b-9717-5cea346adfb2" src="/sites/default/files/inline-images/Prog2.png" /></p> <p>With Dynamics 365 SDK you have to use <code>SuppressDuplicateDetection</code> optional parameter in your request (<em>CreateRequest </em>or <em>UpdateRequest</em>) if you want the configured duplicate detection rules to run and throw an exception while creating or updating a record.<br /> So set its value to <em>false </em>in your code to trigger duplicate detection rules.<br /> You will get the following error if you try to create a duplicate record:<br /> <em>A record was not created or updated because a duplicate of the current record already exists</em></p> <p>Below a C# example to create with the SDK a contact named "John Doe" with activation of duplicate detection rules.</p> <pre> <code class="language-cs">namespace CrmDuplicateDetection { class Program { static void Main(string[] args) { CrmServiceClient.MaxConnectionTimeout = TimeSpan.FromHours(3); var connectionString = WebConfigurationManager.AppSettings["connectionString"]; var client = new CrmServiceClient(connectionString); var contact = new Entity("contact") { ["firstname"] = "John", ["lastname"] = "Doe" }; var createRequest = new CreateRequest { Target = contact }; // Pass parameter 'SuppressDuplicateDetection' to trigger Duplicate Detection Rules createRequest["SuppressDuplicateDetection"] = false; var response = (CreateResponse)client.Execute(createRequest); } } }</code></pre> <h4>Alternate keys</h4> <p>If you try to create a duplicate record programmatically with Dynamics 365 SDK you will get an error even if you set parameter <code>SuppressDuplicateDetection</code> to <em>false </em>in your <em>CreateRequest</em>/<em>UpdateRequest </em>request (and same if you set <code>MSCRM.SuppressDuplicateDetection </code>to <em>false </em>in the header of your Web API request).</p> <p>Below an example of Web API POST request to create a contact with last name and birthdate that already exist in Dataverse.</p> <p><img alt="Prog3" data-entity-type="file" data-entity-uuid="4e4b2b72-300c-4482-aeec-429c11f598ca" src="/sites/default/files/inline-images/Prog3.png" /></p> <h3>Conclusion</h3> <p>As we have seen alternate keys are a strict way to avoid duplicates creation and ensure data correctness.<br /> It is not possible to create duplicate records using Dynamics 365 user interface or Web API/Dynamics 365 SDK.<br /> But alternate keys have some limitations (10 alternate keys max per table, not supported in virtual tables, cannot include columns with field level security enabled, no operator Same First/Last characters in the criteria).<br /> Duplicate detection rules are more flexible when creating duplicate records through the UI (ability to merge records or force the creation of duplicates).<br /> And with Web API/Dynamics 365 SDK you can choose whether you want to apply duplicate detection rules when creating/updating records.<br /> So the choice between duplicate detection rules and alternate keys will depend on whether you want to allow duplicates creation under certain circumstances.<br /> If you never want to create duplicate records (either through the user interface or programmatically) alternate keys are a good solution because the check is done at database level.<br /> But if you want to warn users when they try to create a duplicate record in the UI and allow them to confirm or not the duplicate creation (and use <em>Merge </em>feature) duplicate detection rules are a good choice.<br /> The same is true if you have interfaces between a third-party system and Dynamics 365 (an Azure webjob or Azure function for instance) and want to allow or not creation of duplicates.<br /> But if you have an existing interface that allows the creation of duplicate records and want to change this behavior, you will have to modify the source code of the interface to use <code>SuppressDuplicateDetection</code> parameter to trigger duplicate detection rules.</p> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2025-10/ChatGPT%20Image%20Oct%2020%2C%202025%2C%2006_26_53%20PM.png</div> </div> </div> </div> </div> Wed, 08 Oct 2025 15:57:17 +0000 Stephane Pelhatre 860 at https://dynamics-chronicles.com Dataverse Functions Deep Dive https://dynamics-chronicles.com/article/dataverse-functions-deep-dive <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Dataverse Functions Deep Dive</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/stephane-pelhatre" lang="" about="/user/stephane-pelhatre" typeof="schema:Person" property="schema:name" datatype="" class="username">Stephane Pelhatre</a></span> <span property="schema:dateCreated" content="2025-08-28T12:38:15+00:00" class="field field--name-created field--type-created field--label-hidden">Thu, 08/28/2025 - 14:38</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><h1>Introduction</h1> <p>Microsoft released in 2024 Dataverse low-code plugins. The promise was great : writing plugins using low-code tools. No need to have development skills and use a development environment as Microsoft Visual Studio, write C# code and register plugin in your environment.</p> <p>There are two types of low-code plugins supported in Dataverse : instant and automated. The difference is the trigger. For instant low-code plugin the trigger is manual, the plugin must be explicitly called (for example from a Power Automate flow). Automated low-code plugins work like classic plugins : the trigger is a Dataverse table event. For example you can create an automated low-code plugin on Create event for Account table. Low-code plugin feature is still in preview.</p> <p>Then Microsoft has introduced Power Fx functions (eventually renamed Dataverse functions). Functions are a low-code/no-code way to write reusable logic directly within Dataverse. Functions use Power Fx to execute a set of commands within Dataverse that run on the server-side. The trigger is also manual, so a function must be called explicitly .<br /> The situation is therefore somewhat confusing as instant low-code plugins resemble Dataverse functions and both features are available in preview.<br /> Technically functions and low-code plugins are 2 different tools, with their own Microsoft Learn Docs having their own sections.</p> <p><br /> <img alt="learn1" data-entity-type="file" data-entity-uuid="44b88b89-6a30-4095-989b-760bbb9c8a46" src="/sites/default/files/inline-images/learn1.png" /><br /> <br /> Officially instant low-code plugins are deprioritized and are replaced with Dataverse functions. Automated low-code plugins are still available. You can find more information here : <a href="https://learn.microsoft.com/en-us/power-apps/maker/data-platform/low-code-plug-ins?tabs=instant">Use low-code plug-ins in Dataverse</a></p> <p><img alt="disclaimer" data-entity-type="file" data-entity-uuid="605f4e9b-b739-481f-9a8e-e47499ed96a8" src="/sites/default/files/inline-images/disclaimer.png" /></p> <p>In this article we will first briefly examine low-code plugins. We won't go into detail because, as we will see, they don't really work.<br /> Then we will examine Dataverse functions in detail.</p> <h1>Low-code plugins</h1> <p>Let's briefly talk about low-code plugins.<br /> To access low -code plugins interface you need to install a model-driven app called <strong>Dataverse Accelerator</strong>:</p> <ol> <li>Go to <a href="https://admin.powerplatform.microsoft.com">Power Platform Admin Center</a></li> <li>Click on <strong>Environments </strong>on the left pane.<br /> Then click on the ellipsis (...)  next to your environment, select <strong>Resources </strong>and then <strong>Dynamics 365 apps</strong><br /> <img alt="acc0" data-entity-type="file" data-entity-uuid="030c4df0-e33f-49c0-82b5-f23e20a023bb" src="/sites/default/files/inline-images/accelerator0.png" /><br />  </li> <li>Click on <strong>Install app</strong> on the command bar<br /> <img alt="acc1" data-entity-type="file" data-entity-uuid="fcff52a6-a59b-4706-84c7-68149c785334" src="/sites/default/files/inline-images/accelerator1.png" /><br />  </li> <li>Select <strong>Dataverse Accelerator </strong>in the list and click on <strong>Next</strong><br /> <img alt="acc2" data-entity-type="file" data-entity-uuid="c497a9b0-3e23-454e-abf4-bc433ea3ac87" src="/sites/default/files/inline-images/accelerator2.png" /><br />  </li> <li>Check<strong> I agree to the terms of service</strong> and click on <strong>Install</strong><br /> <img alt="acc3" data-entity-type="file" data-entity-uuid="4f8e8ec3-cbd7-49c0-a2b5-193d159f68ca" src="/sites/default/files/inline-images/accelerator3.png" /><br />  </li> <li>Once the installation is complete Dataverse Accelerator app is available in the list of apps<br /> <img alt="acc4" data-entity-type="file" data-entity-uuid="71c96b13-e889-4785-a26b-92c27d68e791" src="/sites/default/files/inline-images/accelerator4.png" /></li> </ol> <p>Now you can play the app and you will see the interface below:<br /> <img alt="acc5" data-entity-type="file" data-entity-uuid="6ba71fb7-e4c1-4b55-9a06-36a5ea3ae43d" src="/sites/default/files/inline-images/accelerator5.png" /></p> <p>As explained in the introduction there are two types of plugins : instant plugins that must be called explicitly and automated plugins that are triggered by an event on a table.<br /> There is a button to create an instant plugin and another to create an automated plugin. Note that it 's a bit strange to have the option to create an instant plugin as this feature is deprioritized and replaced by Dataverse functions.</p> <p>Then if you click on either of the two buttons ... nothing happens and the screen remains blank. I tried with several Dataverse environments located in different regions (US, Western Europe) with the same result : a blank screen.</p> <p>This problem is understandable for instant plugins as they are replaced by functions. But regarding automated plugin it's a problem : it's impossible to create an automated plugin.<br /> It was working a few months ago (but buggy as hell), but as of the date of writing this article (October 2025),  it no longer works.<br /> Maybe it will work again in the future or maybe Microsoft will remove this feature.</p> <h1>Dataverse Functions</h1> <h2>Introduction</h2> <p><span class="font-[700]">Functions</span><span> are a </span><span class="font-[700]">low-code/no-code way</span><span> to write </span><span class="font-[700]">reusable logic directly within Dataverse</span><span> (running server-side) without having to write C# code or deploying plugins.</span><br /> Functions can have parameters: parameters allow you to pass information between the function and the context that runs it, making it easier to design business logic that can be reused in varying situations. Input parameters are used to provide data to the function, and allow you to control how the function behaves by passing in different values you specify in the Power Fx formula. Output parameters allow you to retrieve the results of a function for further use in your program.</p> <h2>How to create a function</h2> <ol> <li>Open <a href="https://make.powerapps.com/">Power Apps Portal</a> and select your environment</li> <li>Open the solution <span> where you want to build a function</span></li> <li>Click on <strong>New </strong>--&gt; <strong>Automation </strong>--&gt; <strong>Function<br /> <br /> <img alt="funct1" data-entity-type="file" data-entity-uuid="e0c1dbf6-6b79-4ab9-beb1-c17f68779994" src="/sites/default/files/inline-images/function1.png" /></strong><br /> Provide the following details:<br /> <strong>- Display name</strong> : The human-readable name of the function. Can't be changed once created.<br /> <strong>- Description </strong>:<strong> </strong>provide additional context about the function, such as purpose, behavior, and so on. Can't be changed once created.<br /> - <strong>Input parameters(s)</strong> : add required input parameters. Input paramerts are optional.<br />   To add a parameter click on <strong>+ New input parameter<br />   </strong>Following types are supported: Boolean, Datetime, Decimal, Float, Integer, String<br /> <strong>- Output result(s) </strong>: add output results parameters. Output results are optional. To add an output result click on <strong>+ New output result<br /> - Table references </strong>:<strong> </strong>used to associate functions to specific tables. You can select up to five tables to read or write from in your function’s formula.<br /> - <strong>Formula</strong>: custom function that can be used to perform actions or calculations, defined using the Power Fx expression language<br /> <br /> Let's create a simple function to sum two numbers.<br /> I define my function <em>CustomSum </em>with two inputs parameters <em>x</em> and <em>y</em> and one output parameter <em>z</em>.The Power Fx formula to sum the 2 numbers is : <em>{z:x+y}<br /> <img alt="funct3" data-entity-type="file" data-entity-uuid="136c5ff0-ee42-4e87-8bb8-3193944a26d9" src="/sites/default/files/inline-images/function3.png" /></em><br /> <br /> Another example: let's implement a function that creates a contact from two input parameters <em>firstname </em>and <em>lastname</em> (both with string data type)<br /> This time we need to access a table to create a record, so I select 'Contacts' table in <strong>Table references</strong><br /> I also define an output result <em>message</em> to indicate that the contact was created successfully<br /> In the Power Fx formula I use the function<em> Collect() </em>to create the record <pre> <code>Collect(contact, { firstname: firstname, lastname: lastname } ); { message: "New contact " &amp; firstname &amp; "," &amp; lastname &amp; " created" }</code></pre> <br /> <img alt="funct5" data-entity-type="file" data-entity-uuid="80eca25a-afe7-4c8a-bd5f-f6881222e098" src="/sites/default/files/inline-images/function5.png" /><br />  </li> <li>Click on <strong>Save </strong>to create the function<br /> Various components are created and added to the solution:<br /> - a Custom API<br /> - a Function<br /> - a FxExpression<br /> - a custom API Request Parameter for each input parameter (2 in my example)<br /> - a Custom API Response Property for each ouput result  (1 in my example)<br /> <img alt="funct4" data-entity-type="file" data-entity-uuid="aa87857a-3cff-4987-a669-42b989c4a082" src="/sites/default/files/inline-images/function4.png" /></li> </ol> <h2>How to test a function</h2> <p>If you expect to find a button in the command bar when you select your function wihtin the solution, forget it. It is not possible to test a function inside a solution.<br /> To test a function:</p> <ol> <li>Come out of the solution</li> <li>Click on <strong>More </strong>on the left pane and then click on <strong>Discover all</strong><br /> <img alt="test1" data-entity-type="file" data-entity-uuid="dec92a15-b73b-4b91-bc6c-78e72e9883b3" src="/sites/default/files/inline-images/test1.png" /><br />  </li> <li>Click on <strong>Functions<br /> <img alt="test2" data-entity-type="file" data-entity-uuid="bc569094-54e2-4dde-91f8-b32c6c2e111d" src="/sites/default/files/inline-images/test2.png" /></strong><br />  </li> <li>Select your function and click on <strong>Test </strong>on the command bar<br /> <img alt="test3" data-entity-type="file" data-entity-uuid="b8f89e90-38a6-4ca1-91bb-af75b448d081" src="/sites/default/files/inline-images/test3.png" /><br /> <br /> Another way is to click on the ellipsis (...) and select <strong>Test </strong>in the menu<br /> <img alt="test4" data-entity-type="file" data-entity-uuid="5b04f55d-c6bf-4d27-aeb3-3deb9fe61873" src="/sites/default/files/inline-images/test4.png" /><br />  </li> <li>Enter a value for each input parameter and click on <strong>Play</strong><br /> Result is displayed in the text area at the bottom.<br /> In my example with function <em>CustomSum </em>I set x = 10 and y = 5 and we can see that value assigned to output result is 15<br /> Result is : <em><span style="color: rgb(50, 49, 48); font-family: &quot;Segoe UI&quot;, &quot;Segoe UI Web (West European)&quot;, -apple-system, BlinkMacSystemFont, Roboto, &quot;Helvetica Neue&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">{"@odata.context":"https://orgxxxxx.crm.dynamics.com/api/data/v9.0/$metadata#Microsoft.Dynamics.CRM.cr0de_CustomSumResponse","z":15}</span></em><br /> <img alt="test5" data-entity-type="file" data-entity-uuid="3f7b97be-ada1-4da5-8a83-3e77fbdecb78" src="/sites/default/files/inline-images/0_test5.png" /><br /> <br /> For the second function <em>Create Contact</em> I set firstname = 'John' and lastname = 'Doe'. You can see the result below:<br /> <img alt="test6" data-entity-type="file" data-entity-uuid="1a7ed145-cd11-4aaf-ae7b-66f13f7784ce" src="/sites/default/files/inline-images/0_test6.png" /></li> </ol> <h2>How to delete a function</h2> <p>You must be careful if you want to delete a function. If you delete the function component in your solution only the function itself will be deleted, but related components (Custom API, FxExpression, custom API Request Parameters, Custom API Response Properties) will not be deleted. Therefore, you will end up with orphaned components.<br /> To delete the function and all related components you must follow the same steps as for testing a function (see above): come out of the solution, click on <strong>More </strong>then <strong>Discover all </strong>and select <strong>Functions</strong>.<br /> Then you can click on <strong>Delete </strong>on the command bar or click on the ellipsis (...) and select <strong>Delete</strong>.<br /> <img alt="delete" data-entity-type="file" data-entity-uuid="a866fca3-3a7b-4f01-b973-d675d5a6f857" src="/sites/default/files/inline-images/delete_1.png" /></p> <h2>Trace logs</h2> <p>You can access Dataverse functions trace logs in the same place as classic plugins.<br /> Open <strong>Power Platform Environment Settings</strong> app and click on <strong>Plug-in Trace Log</strong> on the left pane.<br /> <img alt="logs1" data-entity-type="file" data-entity-uuid="8bbdb569-991e-4d5a-9e5c-2ccb1c5baad7" src="/sites/default/files/inline-images/logs1.png" /><br />  </p> <p>You have access to execution information (duration, message block, exception, ...)</p> <p><img alt="logs2" data-entity-type="file" data-entity-uuid="f54b19a3-7cee-4282-aa46-d90e1f014021" src="/sites/default/files/inline-images/logs2.png" /></p> <h2>Invoke a function</h2> <p>We now know how to create and test a function, but it's time to use it.<br /> As explained previously, functions are only triggered manually, so you need to call them explicitly.<br /> You can invoke functions in Dataverse from:</p> <ul> <li>a canvas app</li> <li>a custom page in a model driven app</li> <li>a Power Automate flow</li> <li>from another function</li> <li>in your code (Web API)</li> </ul> <p>You can obtain a code snippet of your function that you will then use to call that function:</p> <ol> <li>Open <a href="https://make.powerapps.com/">Power Apps Portal</a> and select your environment</li> <li>Click on <strong>More </strong>on the left pane and then click on <strong>Discover all</strong></li> <li>Click on <strong>Functions</strong></li> <li>Select you function and click on <strong>Copy code snippet</strong> on the command bar<br /> <img alt="snip1" data-entity-type="file" data-entity-uuid="439b7a85-4f1b-408b-ab80-fdb76a8ce0b2" src="/sites/default/files/inline-images/snippet1.png" /><br /> <br /> Another way is to click on the ellipsis (...) and select <strong>Copy code snippet </strong>in the menu<br /> <img alt="snip2" data-entity-type="file" data-entity-uuid="b89ddb6b-6339-4b0a-a2e2-789f5839cf52" src="/sites/default/files/inline-images/snippet2.png" /></li> </ol> <p>Below the code snippet for my functions <em>CustomSum </em>and <em>Create contact</em></p> <pre> <code>Environment.cr0de_CustomSum({x:Value,y:Value}) Environment.cr0de_Createcontact({firstname:Value,lastname:Value})</code></pre> <h3>Invoke a function from the Dataverse Web API</h3> <p>You can invoke a Dataverse function from the Web API with a POST request<br /> Example with the function <em>CustomSum</em>:</p> <pre> <code class="language-http">POST [Organization URI]/api/data/v9.2/cr0de_CustomSum OData-MaxVersion: 4.0 OData-Version: 4.0 Content-Type: application/json; charset=utf-8 { "x":4, "y":5 }</code></pre> <p>Here the result I get with the plugin WebAPI Launcher in XrmtoolBox:<br /> <img alt="invoke10" data-entity-type="file" data-entity-uuid="44eb24cb-10c3-456e-ae83-0e83343dd8dd" src="/sites/default/files/inline-images/0_invoke1.png" /></p> <h3>Invoke a function from within another function</h3> <p>You can invoke a function from any other function.<br /> To invoke a function within another function, use the syntax: <code>Environment.MyFunction({inputParam1: value1, inputParam2: value2, ... inputParamN: valueN})</code>.<br /> Since the output is always a record, use the dot notation to access the output parameters.<br /> For example, if the function <em>MyFunction </em>has two output parameters defined as out1 and out2, you can access them in either of these two ways:</p> <ul> <li><code>Environment.ExistingFunction({inputParam1: value1, inputParam2: value2, ... inputParamN: valueN}).out1</code></li> <li><code>Environment.ExistingFunction({inputParam1: value1, inputParam2: value2, ... inputParamN: valueN}).out2</code></li> </ul> <p>To demonstrate this, I create a new function <em>CustomSum2 </em>to add 2 and 3. To perform this operation I use the first function <em>CustomSum.</em><br /> The new function <em>CustomSum2</em> has no input parameter and one output result <em>result </em>(Integer type).<br /> Power Fx formula of the new function:<br /> {<br /> <em>    result:Environment.cr0de_CustomSum({x:2,y:3}).z<br /> }</em></p> <p><img alt="invoke2" data-entity-type="file" data-entity-uuid="eaac6da7-5374-4492-a892-214c80c44ab3" src="/sites/default/files/inline-images/invoke2.png" /></p> <h3>Invoke a function from a Power Automate cloud flow</h3> <p>To use a function in a cloud flow:</p> <ol> <li>Add a step to your flow</li> <li>select <strong>Microsoft Dataverse</strong> connector</li> <li>select <strong>Perform an unbound action</strong> action</li> <li>In <strong>Action Name</strong>, select the function (<em>cr0de_CustomSum</em> in my example) and provide values for the input parameters (x = 6 and y = 3 in my example)<br /> <img alt="Invoke3" data-entity-type="file" data-entity-uuid="4d9ea59f-c1d5-4363-b9a0-7ec590538a01" src="/sites/default/files/inline-images/invoke3.png" /><br />  </li> <li>Add a <strong>compose </strong>action using the output of the unbound action as an input so that you can access the results of the function as needed</li> </ol> <p>Below the result:<br /> <img alt="invoke4" data-entity-type="file" data-entity-uuid="907196bc-9660-40ae-aac2-7ccc1a6eb670" src="/sites/default/files/inline-images/0_invoke4.png" /></p> <h2>Limitations with functions</h2> <p>Functions have some limitations:</p> <ul> <li>Max 5 tables per function</li> <li>Only manual trigger, cannot be automated (that's the goal of low-code plugins)</li> <li>Cannot call external API, limited to Dataverse</li> <li>Still in preview : <span>may have bugs. Not suitable for production</span></li> <li>The following Power Fx function aren't currently supported with functions in Dataverse<br /> <br /> <img alt="limit1" data-entity-type="file" data-entity-uuid="6b6110fe-9eb0-4820-8e80-340d6b89de84" src="/sites/default/files/inline-images/limit1_0.png" /></li> </ul> <p> </p> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2026-01/dataver_functions2.png</div> </div> </div> </div> </div> Thu, 28 Aug 2025 12:38:15 +0000 Stephane Pelhatre 849 at https://dynamics-chronicles.com Recycle Bin Dataverse https://dynamics-chronicles.com/article/recycle-bin-dataverse <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Recycle Bin Dataverse</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/joaoneto" lang="" about="/user/joaoneto" typeof="schema:Person" property="schema:name" datatype="" class="username">joao.neto</a></span> <span property="schema:dateCreated" content="2024-07-04T12:40:48+00:00" class="field field--name-created field--type-created field--label-hidden">Thu, 07/04/2024 - 14:40</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><h3>Intro</h3> <p>Have you deleted some Dataverse records accidentally and you're struggling how to managed it ?</p> <p>The new Recycle Bin feature in preview might be what it solves your problem.</p> <p>It allows Administrator only (in the preview version) to recover user deleted records up to 30 days!</p> <h3>How it Works</h3> <p>First we need to ensure that the feature is activated doing it this way_</p> <ul> <li>Going to the Settings -&gt; Product -&gt; Features in the admin center</li> <li><img alt="setup" data-entity-type="file" data-entity-uuid="5611bdbd-09da-4759-a0b9-464c1f1c4e06" height="215" src="/sites/default/files/inline-images/setup01.png" width="555" /></li> <li>Trying to set a recovery interval bigger that 30 days will fail !</li> </ul> <p> </p> <p>Second to view and restore the deleted records:</p> <ul> <li>Go to your environment Settings -&gt; Data Management -&gt; View Deleted Records</li> <li><img alt="view deleted records" data-entity-type="file" data-entity-uuid="766be8e6-ea1f-4cf2-87ea-17333a6b742b" height="395" src="/sites/default/files/inline-images/ViewDRecords01.png" width="659" /></li> <li>And in this view will be able to view and restore all deleted records</li> <li><img alt="view deleted records" data-entity-type="file" data-entity-uuid="375302b3-e2bd-4d52-90dc-f5bf5c14f74b" height="256" src="/sites/default/files/inline-images/ViewDRecords02.png" width="1002" /></li> </ul> <p>Third, you can also use the Organisation Service in .net or the WebApi to:</p> <ul> <li>Retrieve deleted records (web api example)</li> </ul> <pre> <code class="language-python">$query = @() $query += "&lt;fetch top='3' datasource='bin'&gt;" $query += "&lt;entity name='account'&gt;" $query += "&lt;attribute name='name' /&gt;" $query += "&lt;/entity&gt;" $query += "&lt;/fetch&gt;" $uri = $environmentUrl $uri += 'accounts' $uri += '?fetchXml=' + [uri]::EscapeUriString($query -join '') $RetrieveMultipleRequest = @{ Uri = $uri Method = 'Get' Headers = $baseHeaders } Invoke-RestMethod @RetrieveMultipleRequest</code></pre> <p> </p> <ul> <li>Restore deleted records (.net example)</li> </ul> <pre> <code class="language-cs">/// &lt;summary&gt; /// Restores an account record /// &lt;/summary&gt; /// &lt;param name="service"&gt;The authenticated IOrganizationService instance&lt;/param&gt; /// &lt;param name="accountId"&gt;The ID of the deleted account record.&lt;/param&gt; /// &lt;param name="originalName"&gt;The original name value for the account record.&lt;/param&gt; /// &lt;returns&gt;The ID of the restored account&lt;/returns&gt; static Guid RestoreAccountRecordEarlyBound( IOrganizationService service, Guid accountId, string originalName) { Account accountToRestore = new() { Id = accountId, // Appending '(Restored)' to the original name // to demonstrate overwriting a value. Name = originalName + " (Restored)" }; RestoreRequest&lt;Account&gt; request = new() { Target = accountToRestore }; var response = (RestoreResponse)service.Execute(request); return response.id; }</code></pre> <ul> </ul> <h3>Points in atenttion</h3> <ul> <li>Not all tables are enabled for Recycle bin and Solution components, virtual tables, and elastic tables aren't supported for recycle bin operations <ul> <li>Here's a list of tables that don't support recycle bin <a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/restore-deleted-records?tabs=sdk#tables-not-currently-supported-for-recycle-bin">https://learn.microsoft.com/en-us/power-apps/developer/data-platform/restore-deleted-records?tabs=sdk#tables-not-currently-supported-for-recycle-bin</a></li> </ul> </li> <li>Records deleted through a relationship behavior (Cascade All) aren't recovered when the parent record is restored and this must be managed independently by doing a manual restore of the cascaded records or via a plugin </li> </ul> <p> </p> <h3>Final thoughts</h3> <p>The Dataverse Recycle bin it's a must to regarding business data recovery and we can't wait for the final announce of it's general availability.</p> <p> </p> <p> </p> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2024-08/Cover.png</div> </div> </div> </div> </div> Thu, 04 Jul 2024 12:40:48 +0000 joao.neto 803 at https://dynamics-chronicles.com Dataverse Elastic Tables Deep Dive Tutorial https://dynamics-chronicles.com/article/dataverse-elastic-tables-deep-dive-tutorial <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Dataverse Elastic Tables Deep Dive Tutorial</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/joaoneto" lang="" about="/user/joaoneto" typeof="schema:Person" property="schema:name" datatype="" class="username">joao.neto</a></span> <span property="schema:dateCreated" content="2024-06-27T12:46:41+00:00" class="field field--name-created field--type-created field--label-hidden">Thu, 06/27/2024 - 14:46</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p><em>Dataverse Elastic Tables Deep Dive Tutorial</em></p> <h2>Intro</h2> <p>Said to be the "Super Heros" of the information world, that they're super fast and can store lots of information and that they're a game changer for storing massive loads of data.</p> <p>I went to undercover what's behind the scenes of this Elastic Tables feature and check if the rumours are fake or not!</p> <h2>Principles and Architecture</h2> <p><span><span>Microsoft use Cosmos DB instead of azure SQL to store data, meaning they have a special column called Partition ID that organizes information in a "folders" like structure.</span></span></p> <img alt="Dataverse Elastic Tables Deep Dive Tutorial" data-entity-type="file" data-entity-uuid="91e65d2a-0e56-4924-a2c7-301931f858bd" height="173" src="/sites/default/files/inline-images/CosmosDBPartitions.jpg" width="351" class="align-center" /> <p><strong><span><span><span>Partitioning and Horizontal Scaling:</span></span></span></strong></p> <ul type="circle"> <li><span><span>Elastic tables use Azure Cosmos DB partitioning to scale individual tables</span></span></li> <li><span><span>Each elastic table contains a system-defined Partition Id column</span></span></li> <li><span><span>Rows are divided into distinct subsets (logical partitions) based on the value of the partitionid column, like storing rows per in folder by its partitionId</span></span></li> <li><span><span>Choosing a consistent partitioning strategy is crucial for optimal performance</span></span></li> </ul> <p><strong><span><span><span>Unique References and Partitioning:</span></span></span></strong></p> <ul type="circle"> <li><span><span>Unique references for elastic tables combine the primary key and the partitionid value</span></span></li> <li><span><span>There is no limit on the number of logical partitions</span></span></li> <li><span><span>If you don’t set a partitionid value, it remains null</span></span></li> <li><span><span>Custom partitionid values allow multiple records with the same primary key but different partition IDs</span></span></li> <li><span><span>PartitionId cannot be changed after creating a row</span></span></li> </ul> <p><strong><span><span><span>Consistency level</span></span></span></strong></p> <p><span><span>Elastic table supports strong consistency during a logical session. A logical session is a connection between a client and Dataverse. When a client performs a write operation on an elastic table, it receives a session token that uniquely identifies the logical session. To have strong consistency, you must maintain the logical session context by including the session token with all subsequent requests.</span></span></p> <p><span><span>Session tokens ensure that all the read operations that are performed during the same logical session context return the most recent write that was made during that logical session. In other words, session tokens ensure that reads always honor the read-your-writes and write-follows-reads guarantees during a logical session. If a different logical session performs a write operation, other logical sessions might not immediately detect those changes.</span></span></p> <p><strong><span><span><span>Transactional behavior</span></span></span></strong></p> <p><span><span>For example, you have a synchronous plug-in step that is registered on the PostOperation stage of the Create message on an elastic table. In this case, an error that occurs in the plug-in does not roll back the record that is created in Dataverse. You should always avoid intentionally canceling any operation by throwing InvalidPluginExecutionException in the PreOperation or PostOperation synchronous stage. If the error is thrown after the Main operation, the request returns an error, but the data operation succeeds. Any write operations that are started in the PreOperation stage succeed.</span></span></p> <p><span><span>However, you should always apply validation rules in a plug-in that is registered for the PreValidation synchronous stage. Validation is the purpose of this stage. Even when you use elastic tables, the request returns an error, and the data operation won't begin.</span></span></p> <p><strong><span><span><span>Expire data by using Time to live</span></span></span></strong></p> <p><span><span>Dataverse automatically includes a Time to live integer column with elastic tables. This column has the schema name TTLInSeconds and the logical name ttlinseconds.</span></span></p> <p><span><span>When a value is set in this column, it defines the amount of time, in seconds, before the row expires and is automatically deleted from database. If no value is set, the record persists indefinitely, just like standard tables.</span></span></p> <p><strong><span><span><span>Flexible schema with JSON columns</span></span></span></strong></p> <p><span><span>Elastic tables enable you to store and query data with varying structures, without the need for predefined schemas or migrations. There's no need to write custom code to map the imported data into a fixed schema. More information: Developer guide: Query JSON columns in elastic tables Elastic tables enable you to store and query data with varying structures, without the need for predefined schemas or migrations. There's no need to write custom code to map the imported data into a fixed schema. More information: Developer guide: Query JSON columns in elastic tables.</span></span></p> <h2><span><span><span>Limitations</span></span></span></h2> <p><span><span><span>Table features currently not supported with elastic tables:</span></span></span></p> <ul type="disc"> <li><span><span>Business rules</span></span></li> <li><span><span>Charts</span></span></li> <li><span><span>Business process flows</span></span></li> <li><span><span>One Dataverse connector for Power BI</span></span></li> <li><span><span>Many-to-many (N:N) relationships to standard tables</span></span></li> <li><span><span>Alternate key</span></span></li> <li><span><span>Duplicate detection</span></span></li> <li><span><span>Calculated and rollup columns</span></span></li> <li><span><span>Currency columns</span></span></li> <li><span><span>Column comparison in queries</span></span></li> <li><span><span>Table sharing</span></span></li> <li><span><span>Composite indexes</span></span></li> <li><span><span>Cascade operations: Delete, Reparent, Assign, Share, Unshare</span></span></li> <li><span><span>Ordering on lookup columns</span></span></li> <li><span><span>Aggregate queries:</span></span> <ul type="circle"> <li><span><span>Distinct value of attribute1 while orderby on attribute2 value</span></span></li> <li><span><span>Pagination when having multiple distincts</span></span></li> <li><span><span>Distinct with multiple order by</span></span></li> <li><span><span>Order by and group by together</span></span></li> <li><span><span>Group by on link entity (left outer join)</span></span></li> <li><span><span>Distinct on user owned tables </span></span></li> </ul> </li> <li><span><span>Table connections</span></span></li> <li><span><span>Access teams</span></span></li> <li><span><span>Queues</span></span></li> <li><span><span>Attachment</span></span></li> </ul> <p><span><span><span>Currently, you can't add the following types of columns:</span></span></span></p> <ul type="disc"> <li><span><span>Money (MoneyAttributeMetadata)</span></span></li> <li><span><span>MultiSelectPicklist (MultiSelectPicklistAttributeMetadata)</span></span></li> <li><span><span>State (StateAttributeMetadata)</span></span></li> <li><span><span>Status (StatusAttributeMetadata)</span></span></li> <li><span><span>Image (ImageAttributeMetadata)</span></span></li> <li><span><span>Calculated, Rollup, or Formula Columns</span></span></li> <li><span><span>Currency</span></span></li> <li><span><span>Lookup based on the Customer option</span></span></li> </ul> <p><strong><span><span>Relationships with elastic tables:</span></span></strong></p> <ul type="disc"> <li><span><span>Doesn't support creating many-to-many relationships </span></span></li> <li><span><span>One-to-many relationships are supported for elastic tables with the following limitations:</span></span> <ul> <li><span><span>Cascading isn't supported. Cascading behavior must be set to Cascade.None when the relationship is created</span></span></li> <li><span><span>Formatted values for lookup columns aren't returned when the following conditions are true</span></span></li> <li><span><span>The table that is retrieved is a standard table, and the lookup refers to an elastic table</span></span></li> </ul> </li> <li><span><span>You're using a custom elastic table partitionid value. In other words, the partitionid value is set to something other than the default value (null).</span></span></li> <li><span><span>Elastic tables support one-to-many relationships, and related rows can be retrieved when a record is retrieved. Related records can't be included in a query</span></span></li> </ul> <p><strong><span><span>Also: </span></span></strong></p> <ul> <li><span><span>Duplicate detection or adding activities aren't supported</span></span></li> <li><span><span><span>Return related rows in a query</span></span></span></li> <li><span><span>Elastic tables don't currently support returning related rows when a query is run. If you try to return related rows</span></span></li> <li><span><span>Deep insert is not supported with elastic tables. Each related record needs to be created independently</span></span><span><span> </span></span></li> <li><span><span>Elastic tables don't support multi-record transactions</span></span></li> <li><span><span>Elastic tables also don't support grouping requests in a single database transaction that uses the SDK ExecuteTransactionRequest class or in a Web API $batch operation changeset. Currently, these operations succeed but aren't atomic</span></span></li> </ul> <h2>Hands-on</h2> <p>After the theoretical side of the elastic tables let's demystify them by putting our hands-on creating and playing with some of the standard features.</p> <h3>Creating an elastic table</h3> <p><span><span>On the Power Apps maker portal inside your solution create a new table and then select the type elastic.</span></span></p> <p><img alt="Dataverse Elastic Tables Deep Dive Tutorial" data-entity-type="file" data-entity-uuid="a0496f2a-fd21-4e1a-9f21-1ac3e88615b2" height="529" src="/sites/default/files/inline-images/table01.png" width="383" /></p> <p>In the table properties as shown in the image below we can check that some of the default table features aren't available for the elastic tables (creating activities, duplicate detection rules, connection, SharePoint document management, ...)</p> <p><img alt="Dataverse Elastic Tables Deep Dive Tutorial" data-entity-type="file" data-entity-uuid="072a9abe-0636-40d3-9bea-c7e06cbc1e02" height="526" src="/sites/default/files/inline-images/table02.png" width="391" /></p> <h3>Exploring the elastic table Columns</h3> <p>Creating a new column on the elastic table has pretty much the same look &amp; feel except for the missing options in the data type choice list (Currency, Formula, Customer Lookup).</p> <p>A new option arises with the elastic tables within the data type single line of text we can now chose a new format, the JSON.</p> <p><img alt="Dataverse Elastic Tables Deep Dive Tutorial" data-entity-type="file" data-entity-uuid="bb73c162-47c9-460c-b570-e77ae0ee8095" height="432" src="/sites/default/files/inline-images/JsonColumn01.png" width="215" /></p> <p>We now have our table created and let's see the aspect of some of the default columns present in it with our JSON Column in the image below.</p> <p>Two new columns are present:</p> <ul> <li>Partition ID: as explained above it defines the logical compartiment to store the record, it cannot be changed after the record creation</li> <li>Time to Live: measured in seconds and determine the time the record will remain in the table</li> </ul> <p><img alt="Elastic Table View" data-entity-type="file" data-entity-uuid="a99ed98e-8e02-4c52-81ea-9c7abff673e7" height="148" src="/sites/default/files/inline-images/ElasticTableView01.png" width="1191" /></p> <p><span><span><span><span>Considerations around the partitionid column that should meet these criteria:</span></span></span></span></p> <ul> <li><span><span>The values in it don't change. After a row is created that has a partitionid value, you can't change it</span></span></li> <li><span><span>It has a high cardinality value. In other words, the property should have a wide range of possible values. Each logical partition can store 20 gigabytes (GB) of data. Therefore, by choosing a partitionid value that has a wide range of possible values, you ensure that the table can scale without reaching limits for any specific logical partition</span></span></li> <li><span><span>It spreads data as evenly as possible among all logical partitions</span></span></li> <li><span><span>No values are larger than 1,024 bytes</span></span></li> <li><span><span>No values contain slashes (/), angle brackets (&lt;, &gt;), asterisks (*), percent signs (%), ampersands (&amp;), colons (:), backslashes (\), question marks (?), or plus signs (+). These characters aren't supported for alternate keys</span></span></li> </ul> <p><span><span>If a partitionid value isn't specified for a row, Dataverse uses the primary key value as the default partitionid value. For write-heavy tables of any size, or for cases where rows are mostly retrieved by using the primary key, the primary key is a great choice for the partitionid column.</span></span></p> <h3>When querying the JSON column:</h3> <p>Using the the default filter in the table view only uses standard text filtering options so here we will only be able to check for the presence of strings inside the JSON but no more than this.</p> <p><img alt="Dataverse Elastic Tables Deep Dive Tutorial" data-entity-type="file" data-entity-uuid="055f4a4b-b39d-45ed-9cb6-36db0d72831a" height="351" src="/sites/default/files/inline-images/JsonColumnFiltering01.png" width="284" /></p> <p>However great news comes with You can use the <code>ExecuteCosmosSQLQuery</code> message that enables to run any Cosmos SQL query directly against your elastic table and filter rows based on properties inside JSON.</p> <p>So let's try it in our created table and column !</p> <p>Using the WebAPI we create the following http request, also available using the Dataverse SDK for .NET:</p> <pre> <code class="language-http">/api/data/v9.2/ExecuteCosmosSqlQuery( QueryText=@p1, EntityLogicalName=@p2, QueryParameters=@p3)? @p1='select c.props.sc_name as name, c.props.sc_jsoncolumn as json, c.props.sc_jsoncolumn.age as age from c where c.props.sc_jsoncolumn.car=@car and c.props.sc_jsoncolumn.age &gt;= @age' &amp;@p2='sc_elasticpoc' &amp;@p3={'Keys':['@car', '@age'],'Values':[{'Type':'System.String','Value':'Mustang'} , {'Type':'System.Int32','Value': '20'}]}</code></pre> <p>where "sc_elasticpoc" is our table, the "sc_jsoncolumn" is our JSON column and c.props is the prefix on the schema name of the columns. In this prefix, <code>c</code> is an alias or shorthand notation for the elastic table that is being queried.</p> <p>And the result is:</p> <pre> <code class="language-http">{ "@odata.context":"https://orgf9a090a4.crm17.dynamics.com/api/data/v9.2/$metadata#expando/$entity" ,"@odata.type":"#Microsoft.Dynamics.CRM.expando","PagingCookie":"","HasMore":false,"Result@odata.type":"#Collection(Microsoft.Dynamics.CRM.expando)" ,"Result": [{"@odata.type":"#Microsoft.Dynamics.CRM.expando","name":"elastic 2","age":20 ,"json": {"@odata.type":"#Microsoft.Dynamics.CRM.expando","name":"Paul","age":20,"car":"Mustang"}}] }</code></pre> <h3>Final considerations between Elastic and Standard tables:</h3> <p><strong>Performance:</strong></p> <p>As some announcements might let us think that elastic tables offer super performance over standard tables there are some studies that shows that's not quite the reality like in the Piotr Gaszewski blog (<a href="https://piotrgaszewski.hashnode.dev/dataverse-elastic-tables">https://piotrgaszewski.hashnode.dev/dataverse-elastic-tables</a>) that states:</p> <ul> <li> <p><span><span>Standard Create, Update, Delete operations: takes the same or a little bit more time to execute in elastic tables than in standard ones</span></span></p> </li> <li> <p><span><span>Multiple Create, Update, Delete operations: <span><span>takes significantly less time to execute in elastic tables than in standard ones (1/2 to 1/10)</span></span></span></span></p> </li> <li> <p><span><span>Retrieve data operations: takes 2 to 3 times longer <span><span><span><span>in elastic tables than in standard ones </span></span></span></span></span></span></p> </li> </ul> <p><span><span>So elastic tables offers significantly less performance execution times when using single atomic operation, however when working with multiple records on the same request it offers some advantages. </span></span></p> <p><strong><span><span>Service API Limits:</span></span></strong></p> <p><span><span>As per MS, Elastic Tables are still exposed to the Dataverse API throttling limits and bulk operation messages should be used to increase the throughput for high volume operations.</span></span></p> <p><strong><span><span>Is security model user compatible:</span></span></strong></p> <p><span><span>MS says yes that Elastic tables adhere to the Dataverse security model using</span></span></p> <ul type="disc"> <li><span><span>an User or Organisation level ownership</span></span></li> <li><span><span>field level security</span></span></li> </ul> <p><strong><span><span>And yes audit is also available for elastic tables!</span></span></strong></p> <h2>Conclusion:</h2> <p>Elastic tables enlarges the table types offer in dataverse, but it's to take with caution in my opinion because these pretty tables aren't the way to go to standard development in a relation database context, since table lookups aren't available ootb and since many other table features aren't also available which reduces a lot the scope of action of these tables.</p> <p>Regarding performance finally they're also not the way to go if you search for increasing CRUD performances, except using multiple CRUD operations.</p> <p>So my final take on the elastic tables would be for storing large datasets of data and mainly for IoT data that would managed by a service layer capable of using multiple CRUD operations and then consuming the data on a business intelligence scheme (as an example only).</p> <h2 class="title">Dataverse Elastic Tables Deep Dive Tutorial</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2024-06/output%20%281%29.jpg</div> </div> </div> </div> </div> Thu, 27 Jun 2024 12:46:41 +0000 joao.neto 778 at https://dynamics-chronicles.com Power Platform Dataflow Tutorial Deep Dive https://dynamics-chronicles.com/article/power-platform-dataflow-tutorial-deep-dive <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Power Platform Dataflow Tutorial Deep Dive</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/stephane-pelhatre" lang="" about="/user/stephane-pelhatre" typeof="schema:Person" property="schema:name" datatype="" class="username">Stephane Pelhatre</a></span> <span property="schema:dateCreated" content="2024-06-17T12:35:36+00:00" class="field field--name-created field--type-created field--label-hidden">Mon, 06/17/2024 - 14:35</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p><em>Power Platform Dataflow Tutorial Deep Dive</em></p> <h2>Introduction</h2> <p>Dataflows are a self-service, cloud-based, data preparation technology. Dataflows enable customers to ingest, transform, and load data into Dataverse environments, Power BI workspaces, or an organization's Azure Data Lake Storage account.<br /> Dataflows are authored by using Power Query, a unified data connectivity and preparation experience already featured in many Microsoft products, including Excel and Power BI.<br /> Customers can trigger dataflows to run either on demand or automatically on a schedule.</p> <p>Dataflows are featured in multiple Microsoft products and don't require a dataflow-specific license to be created or run. They are available in Power Apps, Power BI, and Dynamics 365 Customer Insights.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="b015a862-560c-4d05-8ef8-59f2003813ee" src="/sites/default/files/inline-images/dataflow-function_0.png" /></p> <p>The previous image shows an overall view of how a dataflow is defined.<br /> A dataflow gets data from different data sources.<br /> Then, based on the transformations configured with the Power Query editor, the dataflow transforms the data by using the dataflow engine.<br /> Finally, data are loaded to the output destination, which can be a Power Platform environment, a Power BI workspace, or the organization's Azure Data Lake Storage account.</p> <p>Dataflows are cloud-based. A dataflow is stored and runs in the cloud. However, if a data source is on-premises, an on-premises data gateway can be used to extract the data to the cloud.</p> <p>Power Query is the data transformation engine used in dataflow. This engine supports many transformations. It also uses a graphical user interface called Power Query Editor.</p> <p>In this article we will study the use of dataflow in the Power Platform through a tutorial.</p> <h2>Dataflow in practice</h2> <p>In this tutorial we will import a CSV file into a Dataverse custom table.</p> <h3>Create a dataflow</h3> <p>To create a dataflow just go to make.powerapps.com on your Power Platform environment and select 'Dataflows' in the left pane.<br /> Then click on '+ New dataflow' on the top.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="f058575d-d04f-413b-baeb-ba011da864e2" src="/sites/default/files/inline-images/powerapps.png" /></p> <p>You will get the following screen:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="cecb17d4-5335-4e50-a572-f988c10b1094" src="/sites/default/files/inline-images/firsst%20screen_0.png" /></p> <p>I named my dataflow 'Import persons'.</p> <p>If you want to load data to an Azure data lake for analytical purposes check 'Analytical entities only'.<br /> In this tutorial I leave the checkbox unchecked as I want to load data into a Dataverse table.</p> <h3>Data sources</h3> <p>First step is to select a data source.<br /> Around 50 data sources are available as you can see below.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="6ba0eb96-fe18-4606-9edf-79aaeca798b4" src="/sites/default/files/inline-images/sources_0.png" /></p> <p>Main types of source:</p> <ul> <li><strong>Files </strong>: Excel, Text/CSV, XML/JSON, ...</li> <li><strong>Databases </strong>: SQL Server, Oracle, MySQL, PostgreSQL, ...</li> <li><strong>Azure </strong>: Azure SQL Database, Synapse Analytics, Blobs, Data Lake Storage Gen2, ...</li> <li><strong>Online services</strong> : Sharepoint, Exchange, Google Analytics, ...</li> </ul> <p>In this tutorial I will use a CSV file. You can directly drag&amp;drop your file in the interface.</p> <p><img alt="Excel_connect" data-entity-type="file" data-entity-uuid="e62f4ac0-7ba2-43f9-bdaf-20fa78f64f44" src="/sites/default/files/inline-images/Excel%20connection.png" /></p> <p>You get a preview of your file. Then click on 'Transform Data'.</p> <p><img alt="excel preview" data-entity-type="file" data-entity-uuid="24168957-2b84-4873-9627-c13313eec1b3" src="/sites/default/files/inline-images/excel%20preview_1.png" /></p> <h3>Data filtering</h3> <p>You get the screen of Power Query Editor</p> <p>At the top we have the formula bar (as in Excel or Power-fx). We will see in this article that we can display the current formula, the full script or hide this formula bar.<br /> Below you can see your data.<br /> And on the right side we have the 'Applied steps' pane with all steps/actions performed. It's a little empty because we've just started, but don't worry it will fill up:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="4c6a78c7-0022-4905-ba0a-cc3a84f41852" src="/sites/default/files/inline-images/screen%20home_0.png" /></p> <p>We have several tabs:</p> <ul> <li><strong>Home </strong>: functionalities to filter data and some features of other tabs</li> <li><strong>Transform</strong>: data transformations</li> <li><strong>Add column</strong>: add a new column to apply a transformation</li> <li><strong>View </strong>: display settings</li> </ul> <p>In this tutorial I first click on 'Use first column as headers' in the ribbon as the first line of my CSV file contains column names.</p> <p>The tab 'Home' contains several interesting features: for instance you can filter rows.<br /> To perform that action you first have to select a column and then click on the button 'Filter Rows'.<br /> Another interesting feature is the possibility to remove duplicates.<br /> As previously you first have to select a column and then click on the command 'Remove duplicates' in the menu 'Remove rows'.<br /> In my example I apply this command on the column <em>LastName </em>: we can see in the screenshot below that we have a duplicate in this column (raley - lines 1 and 4)</p> <p><img alt="remove dup" data-entity-type="file" data-entity-uuid="380cf750-ea01-41b0-92be-a2997f4b2b53" src="/sites/default/files/inline-images/remove%20duplicates_0.png" /></p> <p>And we obtain the expected result:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="fc94d39b-daf2-480d-a3ee-384c1480903e" src="/sites/default/files/inline-images/duplicates2_1.png" /></p> <p>You can also remove blank rows or lines with errors.</p> <h3>Transformations</h3> <p>When you have filtered your data you can now apply some transformations.<br /> Click on 'Transform' tab. You will get a ribbon dedicated to transformation features.</p> <p><br /> <img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="ccb08364-224e-40fa-9768-30ac32d89849" src="/sites/default/files/inline-images/ribbon%20transformation.png" /></p> <p>You can perform many data transformations. This is a non-exhaustive list of transformation features:</p> <ul> <li>Transform any column: <ul> <li>Replace values</li> <li>Change type</li> <li>Detect data type</li> <li>Mark column as key</li> <li>Rename column</li> <li>Pivot/Unpivot columns</li> <li>Fill up/down</li> <li>Move column</li> </ul> </li> <li>Transform text column <ul> <li>Split (by delimiter, number of characters, lower/upper case and more)</li> <li>Format (lower/upper case capitalize words, trim, clean and more)</li> <li>Extract (length, first/last characters, range and more)</li> <li>Statistics (count/distinct values)</li> </ul> </li> </ul> <p>As a first example let's remove the characters 'xxx' in the column <em>Firstname</em> (in <em>rickiexxx </em>or <em>xxxryan</em>).<br /> Select column <em>Firstname </em>and then select the command 'Replace values...' in the menu 'Replace values'<br /> In my example I want to remove 'xxx' so I leave 'Replace with' empty.</p> <p><img alt="replace values" data-entity-type="file" data-entity-uuid="be4509a6-65d9-4878-a775-145e7dfb169e" src="/sites/default/files/inline-images/replace%20values.png" /></p> <p>And we obtain the expected result:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="7f6ae239-1fd6-47d7-bae0-a58e53cb276f" src="/sites/default/files/inline-images/result%20replace%202.png" /></p> <p>Note that at the top in the formula bar you can see the source code (Power Query M) of the last command :<br /> <em>Table.ReplaceValue(#"Removed duplicates", "xxx", "", Replacer.ReplaceText, {"Firstname"})</em></p> <p>Now let's change data type of column <em>Age. </em>It is currently a text column and I will change it to a whole number column.<br /> We can see that in line 3 the column <em>Age </em> contains 'abc' : let's say that it is an error as this column should contain only whole numbers.<br /> So I first select column <em>Age </em>and then in the menu 'Data type' I select the item 'Whole number'.<br /> This is the result:</p> <p><img alt="age1" data-entity-type="file" data-entity-uuid="90b74c27-8d2a-4a56-abbf-c3cf73bfd6a3" src="/sites/default/files/inline-images/age1_0.png" /></p> <p>We get an error in line 3. This makes sense as original data 'abc' is not a number.<br /> To solve this problem I select the column <em>Age </em>and select the command 'Replace errors...' in the menu 'Replace values'.<br /> In my example I set value to 99</p> <p><img alt="replace errors" data-entity-type="file" data-entity-uuid="2be1d771-af22-4e71-9b96-34f46be971e2" src="/sites/default/files/inline-images/replace%20errors_0.png" /></p> <p>And I get the expected result:</p> <p><img alt="age2" data-entity-type="file" data-entity-uuid="af77d668-356f-47e6-b7a7-37c67ec48948" src="/sites/default/files/inline-images/age2_0.png" /></p> <p>The menu 'Format' contains several interesting commands:</p> <p><img alt="format" data-entity-type="file" data-entity-uuid="a3dd1341-bf74-4b70-91b0-5ab8501420a0" src="/sites/default/files/inline-images/format.png" /></p> <p>We have the classic commands lowercase, uppercase, trim.<br /> But we also have the nice command 'Capitalize Each Word' (first letter of each word converted into an uppercase letter) and the command 'Clean' that removes all non-printable characters.</p> <p>Now I apply the command 'Capitalize Each Word'  on column <em>FirstName </em>and the command UPPERCASE on the column <em>Lastname</em>.<br /> This is the result:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="7ccc9ad2-5e76-436b-a904-f46a0f689b68" src="/sites/default/files/inline-images/uppercase.png" /></p> <h3>Add new column</h3> <p>Click on 'Add column' tab, you will get a ribbon dedicated to new columns.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="6d5cb8ca-3fb9-4068-acc5-1879e65439ad" src="/sites/default/files/inline-images/ribbon_column.png" /></p> <p>With commands in tab 'Transform' you apply the transformation to the original column (in-place) as we have seen previously.<br /> Commands in tab 'Add column' store the result of the transformation in a new column.<br /> The menu 'Format' we used previously in tab 'Transform' is also in tab 'Add column'.<br /> But if you use one the commands (for instance UPPERCASE) you will see that a new column is created.</p> <p>With the command 'Custom column' you add a new column but you cannot select a built-in command : you have to write your code (formula) in Power Query M language.</p> <p>As an example, let's create a column <em>Fullname </em>that concatenates columns <em>Firstname </em>and <em>Lastname </em>separated by a comma.<br /> Click on the command 'Custom column' and set the parameters:</p> <ul> <li>New column name : <em>Fullname</em></li> <li>Datatype : Text</li> <li>Custom column formula: I use the Power Query M function<em>Text.Combine()</em> with the 2 columns <em>Firstname </em>and <em>Lastname </em>and comma as separator<br /> You only have to type your function in the editor and click on fields in the section 'Available columns(s)' on the right to add the columns to the formula.</li> </ul> <p><img alt="combine" data-entity-type="file" data-entity-uuid="8c5abc3b-2ee0-4426-ac14-438c24265068" src="/sites/default/files/inline-images/combine_0.png" /></p> <p>And we obtain the expected result:</p> <p><img alt="combine2" data-entity-type="file" data-entity-uuid="9e20a444-41ce-4e78-bc06-466bceac4907" src="/sites/default/files/inline-images/combine2_1.png" /></p> <p>Another interesting feature is <strong>Conditional column</strong> : with this feature you can create new columns whose values are based on one or more conditions applied to other columns.</p> <p>Let's create a conditional column named 'Is too young' : the result will be set to TRUE is Age &lt; 18 otherwise result will be FALSE.</p> <p>You just have to click on the command 'Conditional column' and fill the data as below.</p> <p><img alt="condition_column" data-entity-type="file" data-entity-uuid="c87fed9d-e781-42db-896b-e358b9c8d9cc" src="/sites/default/files/inline-images/condtional_column.png" /></p> <p>In my example I use a static value (18) but it is possible to specify another column as value.</p> <p>And this is the result:</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="b596d6c6-b146-4882-92a1-6c4fcd6152fd" src="/sites/default/files/inline-images/too%20young.png" /></p> <p>And now I change the data type of column 'Is too young'. I select the command 'Detect data type' in tab 'Transform': it automatically detects the best data type for the selected column.</p> <p><img alt="detect type" data-entity-type="file" data-entity-uuid="a90d0dd0-0c56-4912-a698-a9d814051c23" src="/sites/default/files/inline-images/detect%20date%20type_1.png" /></p> <p>As this column contains only strings 'TRUE' or 'FALSE' the best data type is logical true/false (boolean).<br /> And as you can see below it worked : the icon in the header of the column has changed to indicates that the column is now a logical column.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="517dc8b0-b942-4e29-9cc8-459f4b507bc1" src="/sites/default/files/inline-images/datatype2_1.png" /></p> <h3>'Applied steps' pane</h3> <p>And what about the 'Applied steps' pane on the right side?<br /> It displays all the steps executed until now.</p> <p>If you want to view the content of a specific step, simply click on the gear wheel on the right.<br /> To remove a step click on the cross on the left.</p> <p><img alt="view_step" data-entity-type="file" data-entity-uuid="3de1e405-dab0-4643-a5e1-13730e31fc38" src="/sites/default/files/inline-images/view_step_0.png" /></p> <p>If you right-click on a step you access a context menu : rename the step, insert a new step, move the step, ...</p> <p><img alt="context menu" data-entity-type="file" data-entity-uuid="c5878b5f-34e9-4671-a2e2-d818eb5ab74d" src="/sites/default/files/inline-images/context%20menu%20step_0.png" /></p> <p>At the bottom of the pane you have a menu 'Step'. If you click on it you will have 3 choices: Off, Query script, Step script (default).<br /> This defines what is displayed in the formula bar.</p> <p><img alt="viewstep2" data-entity-type="file" data-entity-uuid="152237f2-e61b-42c4-ab06-088f83430a34" src="/sites/default/files/inline-images/view_step2.png" /></p> <p>'Off' : hide the formula bar</p> <p>'Step script' : display in the formula bar the formula (code M) of the selected step.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="098112cb-34c6-42be-9c83-2fade00a9d53" src="/sites/default/files/inline-images/formula_2.png" /></p> <p>'Query script' : display in the formula bar the full script (all steps).</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="a84d199b-0bde-4074-b90f-33c93c4180f2" src="/sites/default/files/inline-images/view_step3_0.png" /></p> <p>Next to the 'Step' button there is 'Diagram view' button. It displays in the formula bar the diagram of all steps.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="ce576561-2b0e-4964-9290-ca9db9d8003a" src="/sites/default/files/inline-images/diag1_1.png" /></p> <p>If you click on the '+' in the diagram a context menu is displayed : you can filter data, add a step, etc...</p> <p><img alt="diag2" data-entity-type="file" data-entity-uuid="e8f79485-3f06-4827-b9ce-eedf0b6461bd" src="/sites/default/files/inline-images/view_diagram2.png" /></p> <p>The 'View' tab contains several display settings including those we have just seen (step script, query script, diagram view),</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="0678e527-04e8-4252-981b-aa4c3ceb7262" src="/sites/default/files/inline-images/view%20tab.png" /></p> <h3>Importing data into Dataverse</h3> <p>When you have finished applying transformations to you data you can click on button 'Next' at the bottom of the screen and move to next step to import transformed data into a Dataverse table.</p> <p>You can choose whether you want to load data into a new table or an existing one.<br /> In my example I create a new table <em>Person</em>.<br /> To do that simply name the table and choose which column is your unique primary column.<br /> An automatic mapping is performed.</p> <h3><img alt="export" data-entity-type="file" data-entity-uuid="c049226c-9d49-4b0f-95ff-1c1ad1bbf1ee" src="/sites/default/files/inline-images/export%20dataverse_0.png" /></h3> <p>Then click on 'Publish'.<br /> If the publish is successful you get the following screen:</p> <p><img alt="export2" data-entity-type="file" data-entity-uuid="19d957c0-ed6a-44d4-a1cb-c7b151614908" src="/sites/default/files/inline-images/export%202.png" /></p> <p>Finally you can schedule your dataflow : click on the 3 dots '...' and select 'Edit Refresh settings'</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="cd2a7eb1-39dd-4a04-954c-c125c27af4e4" src="/sites/default/files/inline-images/schedule1.png" /></p> <p>You can run your dataflow manually or schedule it based on a specific frequency.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="78baab5e-ed75-420d-bd24-a7c0221f669b" src="/sites/default/files/inline-images/schedule2.png" /></p> <p>Note that you can get the following message if your refresh frequency exceeds the allowed limit of 48 refreshes per day.</p> <p><img alt="Power Platform Dataflow Tutorial Deep Dive" data-entity-type="file" data-entity-uuid="31088401-e59c-41a4-9ca0-0b8967b9cb88" src="/sites/default/files/inline-images/schedule3.png" /></p> <p>And the result in the Power Platform:</p> <p><br /> <img alt="result" data-entity-type="file" data-entity-uuid="5ac53ee8-d680-40f8-a584-e7f4f1bf6980" src="/sites/default/files/inline-images/result.png" /></p> <h2>Dataflow refresh</h2> <p>Each time you refresh a dataflow, it fetches records from the source and loads data into Dataverse.<br /> If you run the dataflow more than once you can:</p> <ul> <li>Create new records for each dataflow refresh, even if such records already exist in the destination table. In this case you will create duplicates.</li> <li>Create new records if they don't already exist in the table, or update existing records if they already exist in the table. This behavior is called <em>upsert</em>.</li> </ul> <p>Using an alternate key column indicates to the dataflow to upsert records into the destination table, while not selecting a key indicates to the dataflow to create new records in the destination table.</p> <p>For instance if your data source has a column that is different for all rows (a guid or any unique identifier) you can use this column has an alternate key in your Dataverse table.</p> <p>You can get details here: <a href="https://learn.microsoft.com/en-us/power-query/dataflows/get-best-of-standard-dataflows">https://learn.microsoft.com/en-us/power-query/dataflows/get-best-of-standard-dataflows</a></p> <h2>Power Query M language</h2> <p>As mentioned previously Dataflow is based on Power Query and its related language.<br /> The Power Query editor contains many built-in commands to perform basic operations and transformations.<br /> But you will probably have to use the language to write your own transformations. It is what we call 'writing code M'.<br /> The M stands for data Mash-up, as Power Query is all about connecting to various different data sources and “Mashing” them up.<br /> The Power Query M formula language is optimized for building flexible data mashup queries. It's a functional, case sensitive language similar to F#.<br /> Power Query Formula Language is used in a number of Microsoft products such as Power BI Desktop, Excel, and Analysis Services.</p> <p>Details about Power Query M language are out of the scope of this article.<br /> Specifications of the language can be found here:<br /> <a href="https://download.microsoft.com/download/8/1/A/81A62C9B-04D5-4B6D-B162-D28E4D848552/Power%20Query%20M%20Formula%20Language%20Specification%20(July%202019).pdf">https://download.microsoft.com/download/8/1/A/81A62C9B-04D5-4B6D-B162-D28E4D848552/Power%20Query%20M%20Formula%20Language%20Specification%20(July%202019).pdf</a></p> <p>The specification describes the values, expressions, environments and variables, identifiers, and the evaluation model that form the Power Query M language’s basic concepts.</p> <p>The official reference guide : <a href="https://learn.microsoft.com/en-gb/powerquery-m/power-query-m-function-reference">https://learn.microsoft.com/en-gb/powerquery-m/power-query-m-function-reference</a></p> <p>Many categories are available:</p> <ul> <li>    Accessing data functions</li> <li>    Binary functions</li> <li>    Combiner functions</li> <li>    Comparer functions</li> <li>    Date functions</li> <li>    DateTime functions</li> <li>    DateTimeZone functions</li> <li>    Duration functions</li> <li>    Error handling</li> <li>    Expression functions</li> <li>    Function values</li> <li>    List functions</li> <li>    Lines functions</li> <li>    Logical functions</li> <li>    Number functions</li> <li>    Record functions</li> <li>    Replacer functions</li> <li>    Splitter functions</li> <li>    Table functions</li> <li>    Text functions</li> <li>    Time functions</li> <li>    Type functions</li> <li>    Uri functions</li> <li>    Value functions</li> </ul> <p>Some basics about the language:</p> <p>A Power Query M formula language query is composed of formula expression steps that create a mashup query.<br /> A formula expression can be evaluated (computed), yielding a value.<br /> The <strong>let </strong>expression encapsulates a set of values to be computed, assigned names, and then used in a subsequent expression that follows the <strong>in </strong>statement.<br /> For example, a let expression could contain a <strong>Source </strong>variable that equals the value of <strong>Text.Proper</strong>() and yields a text value in proper case.</p> <pre> <code>let Source = Text.Proper("hello world") in Source</code></pre> <p>In this tutorial we wrote a formula to concatenate first name and last name:</p> <pre> <code>Text.Combine({[Firstname],[Lastname]}, " , ")</code></pre> <p>Note that function name (Combine) is prefixed by the category name (Text).<br /> For example : <em>Date.AddMonths()</em>, <em>Splitter.SplitTextByDelimiter()</em></p> <p>Below the full script of this tutorial:</p> <pre> <code>let Source = Csv.Document(Web.Contents("https://crm315857-my.sharepoint.com/personal/admin_crm315857_onmicrosoft_com/Documents/Apps/Microsoft Power Query/Uploaded Files/example1 8.csv"), [Delimiter = ";", Columns = 3, QuoteStyle = QuoteStyle.None]), #"Promoted headers" = Table.PromoteHeaders(Source, [PromoteAllScalars = true]), #"Removed duplicates" = Table.Distinct(#"Promoted headers", {"Lastname"}), #"Replaced value" = Table.ReplaceValue(#"Removed duplicates", "xxx", "", Replacer.ReplaceText, {"Firstname"}), #"Changed column type" = Table.TransformColumnTypes(#"Replaced value", {{"Age", Int64.Type}}), #"Replaced errors" = Table.ReplaceErrorValues(#"Changed column type", {{"Age", 99}}), #"Capitalized each word" = Table.TransformColumns(#"Replaced errors", {{"Firstname", each Text.Proper(_), type nullable text}}), #"Uppercased text" = Table.TransformColumns(#"Capitalized each word", {{"Lastname", each Text.Upper(_), type nullable text}}), #"Added custom" = Table.TransformColumnTypes(Table.AddColumn(#"Uppercased text", "Fullname", each Text.Combine({[Firstname], [Lastname]}, " , ")), {{"Fullname", type text}}), #"Inserted conditional column" = Table.AddColumn(#"Added custom", "Is too young", each if [Age] &lt; 18 then true else false), #"Changed column type 1" = Table.TransformColumnTypes(#"Inserted conditional column", {{"Is too young", type logical}}) in #"Changed column type 1"</code></pre> <p>A major drawback of Power Query editor :<br /> There is no intellisense/auto-completion when you type a formula (like in Microsoft Visual Studio or with Power-fx editor).<br /> This technology is very useful when you do not know the exact name of a function.<br /> And don't forget that code M language is case sensitive: so any case error in a function name and your code will not work.</p> <h2>Mass import</h2> <p>In order to evaluate the performance I have imported to Dataverse a CSV file with 100'000 rows.<br /> <br /> Format of each row:</p> <ul> <li><em>Id </em>: incremental number (unique value in order to perform updates)</li> <li><em>Firstname </em>: string</li> <li><em>Lastname</em>: string</li> <li><em>Age </em>: integer</li> </ul> <p>Transformations applied:</p> <ul> <li>Firstname : Capitalize Each Word</li> <li>Lastname : Uppercase</li> <li>Add new column :  Fullname = Firstname , Lastname</li> </ul> <p>The Dataverse custom table has a column <em>Id </em>as primary column and  alternate key to be able to perform updates.</p> <p>I performed the import first in creation mode : import of transformed data into an empty Dataverse table.<br /> Then I performed an import in update mode : I modified all data in the CSV file (firstname/lastname/age but not Id of course) and imported transformed data in the same Dataverse table.</p> <p>Creation mode : the 100'000 transformed rows have been imported in 58 minutes.<br /> Update mode : the 100'000 transformed rows have been imported in 52 minutes.</p> <h2>Licenses</h2> <p>To create dataflows in Power Apps a license is required (per-user or per-app).</p> <p>If you want to create analytical dataflows that store data in your organization's Azure Data Lake Storage Gen2 account, you or your administrator need access to an Azure subscription and an Azure Data Lake Storage Gen2 account.</p> <h2>Limitations</h2> <ul> <li>Maximum of 48 runs per day for a dataflow</li> <li>Only one owner is currently enabled. If another user wants to modify the dataflow you must change the owner</li> <li>Mapping to polymorphic lookup fields is currently not supported.</li> <li>Mapping to <em>Status </em>and <em>Status Reason</em> fields is currently not supported.</li> <li>Mapping data into multi-line text that includes line break characters isn't supported and the line breaks get removed. Instead, you could use the line break tag &lt;br&gt; to load and preserve multi-line text</li> <li>Mapping to <strong>Choice</strong> fields configured with the multiple select option enabled is only supported under certain conditions. The dataflow only loads data to <strong>Choice</strong> fields with the multiple select option enabled, and a comma-separated list of values (integers) of the labels are used.<br /> For example, if the labels are "Choice1, Choice2, Choice3" with corresponding integer values of "1, 2, 3", then the column values should be "1,3" to select the first and last choices.</li> </ul> <h2 class="title">Power Platform Dataflow Tutorial Deep Dive</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2024-06/Designer.jpeg</div> </div> </div> </div> </div> Mon, 17 Jun 2024 12:35:36 +0000 Stephane Pelhatre 785 at https://dynamics-chronicles.com Dynamics 365 / Dataverse TDS endpoint Control Access and rights https://dynamics-chronicles.com/article/dynamics-365-dataverse-tds-endpoint-control-access-and-rights <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Dynamics 365 / Dataverse TDS endpoint Control Access and rights</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/lloyd-sebag" lang="" about="/user/lloyd-sebag" typeof="schema:Person" property="schema:name" datatype="" class="username">Lloyd Sebag</a></span> <span property="schema:dateCreated" content="2024-03-11T14:29:11+00:00" class="field field--name-created field--type-created field--label-hidden">Mon, 03/11/2024 - 15:29</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p><em>Dynamics 365  Dataverse TDS endpoint Control Access and rights</em></p> <p>Following up on the previous two articles regarding subjects <a href="https://dynamics-chronicles.com/article/how-access-dynamics-365-online-sql-server-database">How to access the Dynamics 365 online SQL Server database</a> and <a href="https://dynamics-chronicles.com/article/dynamics-365-dataverse-access-sql-server-database-c">Dynamics 365 / Dataverse access SQL Server database from C#</a>, here's a third one that will illustrate how to control access to this endpoint.</p> <p>Initially, when this feature was first released, Microsoft hadn't yet implemented a mechanism allowing for selective access to this endpoint. It was an all-or-nothing scenario, where the parameter had to be activated at the level of the entire organization, either enabling it for everyone or for no one. Given that this endpoint granted access to data in a new way and was quite permissive, many companies simply opted not to activate it for security reasons.</p> <p>However, Microsoft has now introduced a new option that could put TDS back in game. This option not only allows for enabling TDS for the entire organization as before, but also enables the selection of who, or rather which role, can use it. This is now possible thanks to the introduction of a new privilege named "prvAllowTDSAccess," which is rather self-explanatory :)</p> <p>Here's the official documentation covering this feature: <a href="https://learn.microsoft.com/en-us/power-platform/admin/control-tds-settings">https://learn.microsoft.com/en-us/power-platform/admin/control-tds-settings</a></p> <h3>Step by Step </h3> <p>To get started, go to admin.powerplatform.microsoft.com and select your environment  </p> <p>Then click on Settings and navigate to Product &gt; Features</p> <p><img alt="Dynamics 365 / Dataverse TDS endpoint Control Access and rights" data-entity-type="file" data-entity-uuid="938e5f32-dfdb-4a20-98b7-5b9b6c50dae7" src="/sites/default/files/inline-images/1_33.png" /></p> <p>Then on the page, at the TDS Chapter level, you will see this new option: Enable user level access control for TDS endpoint</p> <p><img alt="Dynamics 365 / Dataverse TDS endpoint Control Access and rights" data-entity-type="file" data-entity-uuid="89ee6585-3873-4cf1-a342-9d92a2ec6165" src="/sites/default/files/inline-images/2_31.png" /></p> <p>Once activated, all you need to do is go to one of your security roles with which the user will have the right to access TDS and assign this privilege.</p> <p>Go again to admin.powerplatform.microsoft.com and select your environment.</p> <p>Then go to Users &gt; Security Roles</p> <p><img alt="Dynamics 365 / Dataverse TDS endpoint Control Access and rights" data-entity-type="file" data-entity-uuid="a7cf5228-29f9-487d-9efb-af1457eb6bb4" src="/sites/default/files/inline-images/3_29.png" /></p> <p>Then click on Miscellaneous privileges then select Show All Privileges and you should be able to add the prvAllowTDSAccess privilege to this role.</p> <p><img alt="Dynamics 365 / Dataverse TDS endpoint Control Access and rights" data-entity-type="file" data-entity-uuid="842e23e9-2585-4afb-a6d7-3095e3196bdd" src="/sites/default/files/inline-images/4_21.png" /></p> <p>NB : If you are unable to see the list of Miscellaneous privileges you can also assign this via C# code. Explained in this article.  <a href="https://dynamics-chronicles.com/article/assign-privileges-security-role-code-c">https://dynamics-chronicles.com/article/assign-privileges-security-role-code-c</a></p> <p>Thus, all users with this role will be authorized to use the TDS endpoint, the others will receive an error!</p> <h3>Support of Security Roles </h3> <p>An important point to remember, which may seem obvious, the user's security roles will apply to the requested SQL objects. Indeed, SQL actually exposes filteredviews applying the application's security model.</p> <p>Thus, in the example below, the user has the right to access Contact data but not the configuration table. The user will get the following error if they try. </p> <p><img alt="Dynamics 365 / Dataverse TDS endpoint Control Access and rights" data-entity-type="file" data-entity-uuid="ff8f492a-8310-484b-9953-a165b803a53d" src="/sites/default/files/inline-images/5_17.png" /></p> <h2 class="title">Dynamics 365 / Dataverse TDS endpoint Control Access and rights</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2024-03/Dynamics%20365%20%20Dataverse%20TDS%20endpoint%20Control%20Access%20and%20rights.jpeg</div> </div> </div> </div> </div> Mon, 11 Mar 2024 14:29:11 +0000 Lloyd Sebag 763 at https://dynamics-chronicles.com Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal https://dynamics-chronicles.com/article/filtering-dynamics-365-finance-and-operations-dataverse-virtual-entities-customer-portal <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/fabien-leal" lang="" about="/user/fabien-leal" typeof="schema:Person" property="schema:name" datatype="" class="username">Fabien Leal</a></span> <span property="schema:dateCreated" content="2023-04-28T09:35:05+00:00" class="field field--name-created field--type-created field--label-hidden">Fri, 04/28/2023 - 11:35</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p>Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal</p> <p>PowerApps portal helps you exposing CRM data to your customer through a web application.  It works pretty well when the data are persisted in CRM, then we can use all the power of the dataverse platform to propose forms, views and processes to the end customer.</p> <p>The CRM Portal Management application proposes a full set of tools to manage the customer access: Table permission, Web Roles, Content access level, Web Page Access etc... but there are some limitations to it.</p> <p><em>Note: In the scenario the customer is logged in the Portal as a CRM contact.</em></p> <h3>What is this article for?</h3> <p>In this article, I will present a technical approach that we used in a project to circumvent a limitation of the use of Virtual Entities in a PowerApps Portal + CE + FnO setup. Indeed, standard security and integration tools provided by Microsoft <u>does not allow to filter FnO data with the contact context from the Portal.</u></p> <h3>Integration scope</h3> <p>In this scenario, CRM is interfaced with the Customer Portal and with Dynamics 365 for Finance and Operations (twice).</p> <p>For the Accounts, Contacts and all related data (address, electronic address...) the interface with F&amp;O is made with Dual-Write. This scenario involves the usage of the Party entity and adds a new entity name Contact for Party (or Contact for Customer or Vendor) which mimics the possibility to add a contact to several accounts and vice-versa. These entities are standard and included in the Microsoft Dual-Write solutions.</p> <p>Global Address Book integration documentation:</p> <ul> <li><a href="https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/dual-write/party-gab">Party and global address book - Finance &amp; Operations | Dynamics 365 | Microsoft Learn</a></li> </ul> <p>For the Sales tables of F&amp;O we will use Virtual Entities with the Microsoft standard connector. The link between CRM account and Sales Virtual Entity is made through a custom development.</p> <p>Virtual Entities documentations:</p> <ul> <li>Finance and Operations Virtual Entities : <a href="https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/power-platform/admin-reference">Configure Dataverse virtual entities - Finance &amp; Operations | Dynamics 365 | Microsoft Learn</a></li> <li>Link dataverse Account to Sales Data of F&amp;O : <a href="https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/power-platform/entity-modeling#virtual-tabletonative-table-relationship">Entity modeling - Finance &amp; Operations | Dynamics 365 | Microsoft Learn</a></li> </ul> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="06eaef53-e8e6-4c94-bb74-35035ad83523" height="322" src="/sites/default/files/inline-images/data%20model.png" width="776" class="align-center" /> <h3>Customer Portal integration with Finance &amp; Operations</h3> <p>There is a scenario that helps the external users accessing the data in F&amp;O via a anonymous access to it.</p> <p>To create the access, add a new user in FO and give it the "Dataverse Virtual entity anonymous user" role and the appropriate roles to access the desired data. For this example we call it "VirtualEntitiesCRM".</p> <p>Then go to System Administration -&gt; System parameters -&gt; Power Apps Portals. Fill the "Anonymous access user id" with the user you have just created:</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="d3e8bce6-694c-4f73-b90f-37e56c37a2ff" height="349" src="/sites/default/files/inline-images/anonymous%20access.png" width="799" class="align-center" /> <p>Documentation for access to F&amp;O from the Portal: <a href="https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/power-platform/power-portal-reference#anonymous-access-from-power-apps-portals">Power Apps portals with finance and operations - Finance &amp; Operations | Dynamics 365 | Microsoft Learn</a></p> <p>At this time, if we add the virtual entity to the portal, it works. So, what is the problem?</p> <h3>Apply the security to filter data</h3> <p>The access to F&amp;O data is made through a anonymous access, which means the ERP is returning <strong>ALL </strong>the data with no filtering to any customer.</p> <p>Data access to the portal is managed with Table Permissions which mimics the CRM security roles from a portal user (CRM Contact) perspective.</p> <p>In our scenario, we need to display the sales record for all the accounts "attached" to the logged in contact. So, it would be something like this:</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="d827eea7-b86d-4a15-86e5-d4db17044f41" height="256" src="/sites/default/files/inline-images/table%20permissions.png" width="786" class="align-center" /> <p>Let's test the result in our portal:</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="eb461004-e7e7-4159-b9e6-a01b2e1ddc98" src="/sites/default/files/inline-images/Portal%20result%20with%20standard%20security_0.png" class="align-center" /> <ul> </ul> <p>This exact same configuration works with standard CRM entities, but not with virtual ones. So, let's check Virtual entities limitations, in the documentation you can find this sentence:</p> <p>"<span><span><span><span><span><span><span><span><span>•</span></span></span></span><span><span><span><span><span><em><span>Although you can add virtual table columns as a lookup on a grid or other UI views,</span></em></span></span></span></span></span> <span><span><span><span><strong><span><span>you cannot </span></span></strong></span></span></span></span><span><span><span><span><strong><span><span>filter</span></span></strong></span></span></span></span><span><span><span><span><strong><span><span> or sort based on this virtual table lookup column</span></span></strong></span></span></span></span><span><span><span><span><span><span><span>."</span></span></span></span></span></span></span></span></span></span></span></span></p> <p><span><span><span><span><span><span><span><span><span><span><span><span>Source : </span></span></span></span></span></span></span></span></span></span></span></span><a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/virtual-entities/get-started-ve#limitations-of-virtual-tables">Get started with virtual tables (entities) (Microsoft Dataverse) - Power Apps | Microsoft Learn</a></p> <h3>How to resolve this?</h3> <h4>Main concerns:</h4> <ul> <li>Prevent sensitive data to leak on the web</li> <li>Display the appropriate records on the portal</li> <li>Keep acceptable responsiveness</li> </ul> <h4>Prevent sensitive data to leak on the portal</h4> <p>If we create a view with no filter on the portal, we will get all the data from F&amp;O, so we need to remove any data to be returned to the portal.</p> <p>This is the best way to do it: Create a view that returns nothing!</p> <p>The purpose here is to build a view in which we add a dummy filter criteria. This way, we ensure it does not return anything to a logged in user in the portal. In this example we apply the filter on a field which is never used.</p> <p>Add this view on the portal page and you are good to go.</p> <p><em>Note : This filter criteria will also be used it the last part of this solution. </em></p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="d038434e-6e30-4a16-898a-ff404653d4cb" height="371" src="/sites/default/files/inline-images/CRM%20view.png" width="693" class="align-center" /> <h4>Display the appropriate records on the portal</h4> <p>Now that we have a view with no data, we need to fill it with a backend process.</p> <p>The main problem with backend processes is that they run in a user context, not a contact. So, we need to get the logged user context to pass it to our future backend process as it seems not possible to get it natively on the server side.</p> <p>Otherwise, it is possible to get this user ID from the portal and we can send it to the back end with portal filters. So, we will use the field from the previous step to send it via filter and hide it to the portal user:</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="3a04bbf4-2693-4655-a8a8-b07c0db38e3f" height="216" src="/sites/default/files/inline-images/portal%20behavior.png" width="760" class="align-center" /> <p>First, add an attribute filter on the Portal list (the one displaying your dummy view on the portal page ):</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="353ea643-f53f-4672-a0b3-5fe1c0fffc03" height="219" src="/sites/default/files/inline-images/Portal%20filter.png" width="1043" class="align-center" /> <p>We will now use the liquid objects from PowerApps portal to get the user ID and set it in our "Street Number" field. To achieve this, in this same list, go to options and add the following code:</p> <pre> <code class="language-javascript">//get user id var username = '{{user.contactid}}'; $("#textfilter_0").val(username);</code></pre> <p>As we don't want to expose the Contact GUID to the customer, we complete the code to hide the field:</p> <pre> <code class="language-javascript">//Hide filter field $("#textfilter_0").closest("li").show(); $("#textfilter_0").closest("li").parent().parent().hide();</code></pre> <p>In order to make it transparent for the user, we need to filter the view by this criteria automatically. So, we perform a click by script on load of the page:</p> <pre> <code class="language-javascript">$(".btn.btn-default.btn-entitylist-filter-submit").trigger("click");</code></pre> <p>The full code to achieve this is the following one. A timeout has to be set because the click is sometimes fired to quickly on certain browsers:</p> <pre> <code class="language-javascript">$(window).load(function() { //fire filter setTimeout(function() { $(".btn.btn-default.btn-entitylist-filter-submit").trigger("click"); }, 1); }); $(document).ready(function() { //Hide filter field $("#textfilter_0").closest("li").show(); $("#textfilter_0").closest("li").parent().parent().hide(); //get user id var username = '{{user.contactid}}'; $("#textfilter_0").val(username); }</code></pre> <h3>Filtering with backend process</h3> <p>Now we have a view returning no data and a way to pass the contact GUID value to the backend, let's start to build the backend process to achieve this sequence:</p> <img alt="Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal" data-entity-type="file" data-entity-uuid="b75e6bcd-d675-47af-a1e1-7ae084a92072" height="247" src="/sites/default/files/inline-images/sequence_0.png" width="459" class="align-center" /> <p>We will use a synchronous pre-operation plugin triggered on the RetrieveMultiple message to recreate the query when the filter is applied on the customer portal.</p> <p>In our plugin, we will receive a FetchExpression from the portal, it is easier to convert it into QueryExpression to build the new query:</p> <pre> <code class="language-cs">FetchExpression salesQuery = (FetchExpression)context.InputParameters["Query"]; var fetchConversionRequest = new FetchXmlToQueryExpressionRequest() { FetchXml = salesQuery.Query }; var fetchConversionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(fetchConversionRequest); var initialQuery = fetchConversionResponse.Query; </code></pre> <p>First step is to identify that the call to backend is made from the portal, this will allow CRM users to continue working normally with Virtual Entities on their user context. So, we define these variables:</p> <pre> <code class="language-cs">/* Definitions deterArgument is the dummy filter we have set in the previous steps orderColumn is used for default sorting of the query mappingKey is the link from account to the virtual entity magicTrigger is the bool that will say if we need to recompute the query for portal */ string deterArgument = "", mappingKey = "", orderColumn = ""; orderColumn = "mserp_invoicedate"; deterArgument = "mserp_invoiceaddressstreetnumber"; mappingKey = "mserp_fk_synthinvoicingaccount_id"; bool magicTrigger = false;</code></pre> <p>Then, we need to know if we are in the context of a portal query or not. If yes we set the magic trigger to yes, else we leave it to false:</p> <pre> <code class="language-cs">///Checking value of filter to determine if we need to overload the RetrieveMultiple request for the portal if (initialQuery.Criteria.Filters.Count &gt; 0) { List&lt;ConditionExpression&gt; initialConditionList = new List&lt;ConditionExpression&gt;(); Guid contactGuid = new Guid(); foreach (FilterExpression fe in initialQuery.Criteria.Filters) { foreach (ConditionExpression ce in fe.Conditions) { if (ce.AttributeName == deterArgument) //Checking if the determined field for the filter is set { var isGuid = Guid.TryParse(ce.Values.FirstOrDefault().ToString().Replace("%", ""), out contactGuid); if (isGuid) //Logic is triggered only if we pass a Guid { magicTrigger = true; break; } } } } /* Code to continue here */ }</code></pre> <p>Now, we know that we can start building our query. We need to reproduce the schema we tried with Tables permissions, that's to say : <em>contact -&gt; contact for party -&gt; accounts -&gt; Sales Invoices</em>.</p> <p>We also reuse the first query to keep the column set from Dataverse, this prevents to modify the code if we need to changes the columns of the view. We do the same for filters from the portal, if they are note related to the dummy filter we have set, we add it to the query:</p> <pre> <code class="language-cs">if (relatedAccountsList.Count &gt; 0) { //Starting the query on the concerned entity //Reuse of initial column set to keep any standard modifications in powerapps QueryExpression qe = new QueryExpression(entityName) { ColumnSet = initialQuery.ColumnSet }; //Building account filter for portal //"In" operator does not seem to work with virtual entities, so we add several Equals with a OR logical operator FilterExpression fe = new FilterExpression(LogicalOperator.Or); foreach (Entity acc in relatedAccountsList) { if (acc["msdyn_associatedaccountid"] != null) { fe.AddCondition(mappingKey, ConditionOperator.Equal, acc.GetAttributeValue&lt;EntityReference&gt;("msdyn_associatedaccountid").Id); } } qe.Criteria.AddFilter(fe); //we also add the other filters that the user could have added in the portal foreach (FilterExpression initialFe in initialQuery.Criteria.Filters) { foreach (ConditionExpression ce in initialFe.Conditions) { if (ce.AttributeName != deterArgument) //Checking if the determined field for the filter is set { qe.Criteria.AddCondition(ce); } } } /* Code to continue here */ }</code></pre> <p><strong>Important : As written in the comment, "In" operator does not work with virtual entities. If you use it, you will get an error.</strong></p> <p>Main part of the job is made, but we still need to set an order to the query to give the user consistent results. If there is a defined order in the Dataverse view, we use it. Else, we use the column defined in the <em>orderColumn </em>variable:</p> <pre> <code class="language-cs"> if (initialQuery.Orders.Count == 0) { OrderExpression order = new OrderExpression { AttributeName = orderColumn, OrderType = OrderType.Descending }; qe.Orders.Add(order); } else { foreach (OrderExpression oe in initialQuery.Orders) { qe.Orders.Add(oe); } } </code></pre> <p>The paging is no more managed by the portal with this plugin. So, we need to define it in the code.</p> <p>Please note that you need to return a FetchXmlRequest or it will not work on the portal.</p> <pre> <code class="language-cs"> //Define paging PagingInfo paging = new PagingInfo { Count = pagingCount, PageNumber = initialQuery.PageInfo.PageNumber, ReturnTotalRecordCount = true, PagingCookie = null }; qe.PageInfo = paging; //Portal only works with FetchExpression, so we need to convert the QueryExpression before assigning it to the Input Parameter var conversionRequest = new QueryExpressionToFetchXmlRequest { Query = qe }; var conversionResponse = (QueryExpressionToFetchXmlResponse)service.Execute(conversionRequest); var newFetch = new FetchExpression(conversionResponse.FetchXml); context.InputParameters["Query"] = newFetch; </code></pre> <p>And here we are, the query is rebuilt from scratch with all the parameters we need !</p> <h2>Result</h2> <img alt="portal result" data-entity-type="file" data-entity-uuid="f9a79eb6-699a-4fdd-bf04-a89326a00c4b" height="327" src="/sites/default/files/inline-images/portal%20result.png" width="582" class="align-center" /> <p> </p> <h2 class="title">Filtering Dynamics 365 Finance and Operations Dataverse Virtual Entities in Customer Portal</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2023-04/logo%20.png</div> </div> </div> </div> </div> Fri, 28 Apr 2023 09:35:05 +0000 Fabien Leal 642 at https://dynamics-chronicles.com Creating realistic Demo Data for Demo/POC scenarios https://dynamics-chronicles.com/article/creating-realistic-demo-data-demopoc-scenarios <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Creating realistic Demo Data for Demo/POC scenarios</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/david-uhlmann" lang="" about="/user/david-uhlmann" typeof="schema:Person" property="schema:name" datatype="" class="username">David Uhlmann</a></span> <span property="schema:dateCreated" content="2023-04-26T09:57:48+00:00" class="field field--name-created field--type-created field--label-hidden">Wed, 04/26/2023 - 11:57</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p>Ensuring the accuracy and relevance of demo data is crucial in any product demonstration, whether it's during an RFP process or a standalone presentation. Often, we invest significant time and effort into creating an impressive app and/or Power BI dashboard, only to overlook the most critical aspect: the data itself. While we may have some experience in the client's industry, it's not always enough to create data that's truly representative of their unique situation.</p> <p>In the past, we may have turned to Microsoft's demo data, but this can come across as a "<em>low effort approach</em>" that fails to inspire confidence in the product. Instead, it's important to explore other options that provide more realistic and relevant demo data. Why is this important you might ask? All that counts if functionality, right? Wrong! This violates a very important paradigm which is known inside the consulting industry, but still somewhat hidden to some folks, as "<strong><em>perception is reality</em></strong>". TLDR: It is not important what you as a consultant say, show, demonstrate at all. Yes it doesn't matter at all. What is important is how your client, or any counterpart for that matter, is perceiving and feeling at this moment. This is the reality for your counterpart and therefore what matters. And if s/he does not feel "<em>at home"</em> in your app, s/he will less likely approve a six or seven figure budget for it. This is the reason we need realistic data to make the client feel "<em>at home</em>", even during a POC.</p> <p>Fortunately, there are now websites that generate industry-specific demo data that's both accurate and GDPR-compliant. Rather than spending countless hours creating demo data manually, these tools offer a more efficient and effective way to ensure the accuracy and quality of your product demonstration. Don't settle for subpar demo data - check out these websites and take your product demonstrations to the next level. One of those websites is: <a href="https://www.mockaroo.com/">https://www.mockaroo.com/</a></p> <p><img alt="mockaroosample" data-entity-type="file" data-entity-uuid="6e091714-acbb-4475-a6ba-8a013b8d59bc" height="558" src="/sites/default/files/inline-images/mockaroo.PNG" width="881" class="align-left" /></p> <p>Mockaroo.com is not the only website where you can generate realistic sample data for your POC. You can play around with AI-powered features to generate fields for your data. You can download up to 1000 rows at no cost, and up to 10 million rows if you go all out, for example, for a data lake project.</p> <p>When you add fields you have a wide variety of possibilities:</p> <p><img alt="mockaroo categories" data-entity-type="file" data-entity-uuid="4889c89d-a61c-4bda-b62c-dbf450f8a9d9" height="623" src="/sites/default/files/inline-images/mocka2.png" width="883" class="align-left" /></p> <p>This tip stresses that creating a comfortable and relatable experience for the client during a product demonstration/POCs and alike is crucial to the decision to actually win a project. To achieve this, it's essential to use realistic and relevant demo data that makes the client feel "at home" in the app or dashboard that you are presenting. Showing the app's functionality is not enough. What matters is how the client perceives the product. If the client does not feel comfortable and at home in the app, they may be less likely to approve a significant budget for it. Therefore, using accurate and relatable demo data is critical in making the client feel at ease and increasing the chances of a successful product demonstration. Tools like Mockaroo.com and other websites that generate industry-specific demo data offer an efficient and effective way to achieve this goal, ultimately improving the success of product demonstrations.</p></div> </div> Wed, 26 Apr 2023 09:57:48 +0000 David Uhlmann 640 at https://dynamics-chronicles.com Data Manipulation Functional Tools https://dynamics-chronicles.com/article/creating-realistic-demo-data-demopoc-scenarios#comments Query D365/Dataverse Audit logs through API or Organization Service https://dynamics-chronicles.com/article/query-d365dataverse-audit-logs-through-api-or-organization-service <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Query D365/Dataverse Audit logs through API or Organization Service</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/guowei" lang="" about="/user/guowei" typeof="schema:Person" property="schema:name" datatype="" class="username">Guowei</a></span> <span property="schema:dateCreated" content="2023-02-21T03:42:56+00:00" class="field field--name-created field--type-created field--label-hidden">Tue, 02/21/2023 - 04:42</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p>Today, I'd like to show some manners about how to query D365/Dataverse Audit logs through API or Organization Service.</p> <h3>What's the Audit log?</h3> <p>The Auditing feature is designed to meet the external and internal auditing, compliance, security, and governance policies that are common to many enterprises. Dataverse auditing logs changes made to customer records in an environment with a Dataverse database. Dataverse auditing also logs user access through an app or the SDK in an environment.</p> <p>Dataverse auditing is supported on all custom and most customizable tables and columns. Audit logs are stored in Dataverse and consume log storage capacity. Audit logs can be viewed in the Audit History tab for a single record and in the Audit Summary view for all audited operations in a single environment. Audit logs can also be retrieved using the Web API or the Organization Service.</p> <p>About how to configure and enable it in Dataverse you can Google it and there are many articles and documents about that. Because the Auditing feature is a very common function in D365,</p> <p>You can view and search the Audit logs within the D365 CRM in different manners. For example, view the Audit history for a single record or view all Audits logs from the <strong>Audit Summary view</strong>.</p> <p><strong>Audit Logs for a single record</strong></p> <p><img alt="Query D365/Dataverse Audit logs through API or Organization Service" data-entity-type="file" data-entity-uuid="0065b40c-ad26-4a07-93e6-2d68bd9d016b" src="/sites/default/files/inline-images/SingleRecordView.png" /></p> <p><strong>Audit Summary view for all Audit logs</strong></p> <p><img alt="Query D365/Dataverse Audit logs through API or Organization Service" data-entity-type="file" data-entity-uuid="e09391ab-7a59-433b-91fe-b7d7bee88bc1" src="/sites/default/files/inline-images/AuditSummaryView.png" /></p> <h3><strong>Why do we need to query Audit details?</strong></h3> <p>As mentioned above, It's very easy to view the audit logs within the D365 environment, however, sometimes we need to flow the Audit logs to the downstream systems or do some reporting insights based on the whole auditing logs.</p> <p>For this scenario, it's necessary to query the audits log through API or C# Retrieve request.</p> <h3>Audit and Audit details Tables </h3> <ul> <li><strong>Audit </strong>table will have the basic information of change. But to check Audit details like old value, new value we need to get from Audit details. </li> <li><strong>Audit </strong>and<strong> Audit Details </strong>are 2 separate tables, can't get all the details through one query, the relationship is 1 to Many <strong>(1:N)</strong>.</li> <li>Data for auditing events are in the Auditing table. The audit table is read-only.</li> <li>Calling user must have the <em><strong>prvReadAuditSummary</strong></em> privilege to retrieve data from this table.</li> <li>Audit data is not available using the Dataverse <strong>TDS(SQL)</strong> endpoint, as a workaround you can retrieve them through <em><strong>SQL4CDS</strong></em> tool in the <strong>XrmToolbox</strong>.</li> </ul> <p><strong>Important notice:</strong></p> <ul> <li>Large column values included in <strong>AttributeDetail Oldvalue </strong>or <strong>NewValue </strong>properties such as <strong>Email.Description</strong> or <strong>Annotation </strong>are limited(capped) to 5KB or 5000 characters in length.</li> <li>A Capped column value can be recognized by three dots at the end of the text. </li> <li>Because the data is truncated, you cannot use the audit data to restore changes for these columns' values.</li> </ul> <h3>Get Audit Details through API</h3> <p><strong>Testing Environment</strong></p> <ul> <li>D365 CRM online</li> <li>Auditing is enabled and data changes are made to those tables and columns being audited.</li> <li>One Record<em>(97e71d5d-b2b1-ed11-83fd-000d3a370dc4</em>) with 4 Audit logs. </li> <li>Please refer to the screenshot below.</li> </ul> <p><img alt="Query D365/Dataverse Audit logs through API or Organization Service" data-entity-type="file" data-entity-uuid="117265a4-c89c-4bc9-87e7-9fe2e783be22" src="/sites/default/files/inline-images/AuditLogs.png" /></p> <h3>Get Audit </h3> <p><strong>Request URL: </strong></p> <p><em><strong>https://{orgname}.crm.dynamics.com/api/data/v9.2/audits?$filter=objecttypecode eq 'new_test' and _objectid_value eq '97e71d5d-b2b1-ed11-83fd-000d3a370dc4'&amp;$orderby=createdon desc</strong></em></p> <p><strong>Response:</strong></p> <ul> <li>3 records will be retrieved because the <em><strong>Audit Enabled </strong></em>Event won't be responded.</li> <li>Auditid is the primary key of the Audit records.</li> </ul> <p><img alt="Query D365/Dataverse Audit logs through API or Organization Service" data-entity-type="file" data-entity-uuid="6a306c46-5fa6-49db-b98f-8552d6e8873e" src="/sites/default/files/inline-images/AuditsJson.png" /></p> <h3>Get Audit Details</h3> <p>There are <strong>3</strong> ways to get Audit Details.</p> <p><span><span>You can check more from <a href="https://docs.microsoft.com/en-us/power-apps/developer/data-platform/auditing/retrieve-audit-data?tabs=webapi#retrieveauditdetails-message">https://docs.microsoft.com/en-us/power-apps/developer/data-platform/auditing/retrieve-audit-data?tabs=webapi#retrieveauditdetails-message</a></span></span></p> <h3><span><span>1, RetrieveAuditDetails Message</span></span></h3> <p><span><span>Using this message to retrieve the audit details for a single record.</span></span></p> <p><strong><span><span>Request URL</span></span></strong></p> <p><span><span>                  <em><strong>GET [Organization URL]</strong></em> <strong><em>/api/data/v9.2/audits(caf7856c-b2b1-ed11-ba77-281878e731c0)/Microsoft.Dynamics.CRM.RetrieveAuditDetails</em></strong></span></span></p> <p><span><span><strong><em>Response</em></strong></span></span></p> <p><img alt="Query D365/Dataverse Audit logs through API or Organization Service" data-entity-type="file" data-entity-uuid="36a73ac1-af25-4d85-9f67-a8b5bdee3b35" src="/sites/default/files/inline-images/details.png" /></p> <p><strong>C# </strong></p> <pre> <code class="language-cs">/// &lt;summary&gt; /// Returns audit details for the specified audit record /// &lt;/summary&gt; /// &lt;param name="svc"&gt;The IOrganizationService instance to use.&lt;/param&gt; /// &lt;param name="auditid"&gt;The auditid for the audit record.&lt;/param&gt; static void ShowAuditDetail(     IOrganizationService svc,     Guid auditid) {     RetrieveAuditDetailsRequest req =                 new RetrieveAuditDetailsRequest                 {                     AuditId = auditid                 };     RetrieveAuditDetailsResponse resp =         (RetrieveAuditDetailsResponse)svc.Execute(req);     DisplayAuditDetail(resp.AuditDetail); }</code></pre> <p> </p> <h3><span><span>2, RetrieveAttributeChangeHistory Message</span></span></h3> <p>Use this message to retrieve a list of changes for a specific table column.</p> <p><span><span><em><strong>GET [Organization URI]/api/data/v9.2/RetrieveAttributeChangeHistory(Target=@target,AttributeLogicalName=@attributeLogicalName,PagingInfo=@paginginfo)?</strong></em></span></span></p> <h3 id="retrieverecordchangehistory-message">3, RetrieveRecordChangeHistory Message</h3> <p>This message shows the history of data changes for a given record indicated by the Target parameter.</p> <p><em><strong><span><span>GET [Organization URI]/api/data/v9.2/RetrieveRecordChangeHistory(Target=@target,PagingInfo=@paginginfo)?@target={ '@odata.id':'accounts(611e7713-68d7-4622-b552-85060af450bc)'}&amp;@paginginfo={ "PageNumber": 1,"Count": 2,"ReturnTotalRecordCount": true}</span></span></strong></em></p> <p> </p> <p><strong>The End</strong></p> <p><strong>Hope it is helpful.</strong></p> <p><strong>Regards.</strong></p> <h2 class="title">Query D365/Dataverse Audit logs through API or Organization Service</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2023-03/Sans%20titre.png</div> </div> </div> </div> </div> Tue, 21 Feb 2023 03:42:56 +0000 Guowei 625 at https://dynamics-chronicles.com Dynamics 365 Dataverse Change Tracking feature https://dynamics-chronicles.com/article/dynamics-365-dataverse-change-tracking-feature <span property="schema:name" class="field field--name-title field--type-string field--label-hidden">Dynamics 365 Dataverse Change Tracking feature</span> <span rel="schema:author" class="field field--name-uid field--type-entity-reference field--label-hidden"><a title="View user profile." href="/user/guowei" lang="" about="/user/guowei" typeof="schema:Person" property="schema:name" datatype="" class="username">Guowei</a></span> <span property="schema:dateCreated" content="2023-02-20T09:11:34+00:00" class="field field--name-created field--type-created field--label-hidden">Mon, 02/20/2023 - 10:11</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-above"> <div class="field__label">Body</div> <div property="schema:text" class="field__item"><p>Hi, Guys,</p> <p>Today I would like to share the <strong>Change Tracking</strong> feature of Dynamics 365 CRM. </p> <h3>Why enable Change Tracking?</h3> <p><strong>Change Tracking</strong> enables incremental export, only records that have changed are exported.</p> <p>In addition, you can export or retrieve a selected set of data, and then keep the external data warehouse in sync.</p> <p>Suppose we have a lot of integrations of <strong>D365 CRM</strong> with other external systems. We need to keep track of changes done after the last synchronization of data and integrate only those changes to the downstream systems. For this scenario, <strong>Change Tracking</strong> is the best solution.</p> <h3>How to enable Change Tracking?</h3> <p>You can enable<strong> Change tracking</strong> for both system and custom tables(entities).</p> <p>Once you enable the <strong>Track Changes</strong> you cannot disable it.</p> <h3>Using New UI Power Apps Settings</h3> <p>1, Expand <strong>Advanced Options.</strong></p> <p>    For the <strong>Create</strong> scenario, On the <strong>New table page</strong>.</p> <p>    For the <strong>Update</strong> scenario, select a table, and then in<strong> Table Properties</strong> select <strong>Properties</strong>. In the Edit table page, expand <strong>Advanced Options.</strong></p> <p>2, In the <strong>For this table section</strong>, enable the <strong>Track Changes</strong> checkbox.</p> <p><img alt="Dynamics 365 Dataverse Change Tracking feature" data-entity-type="file" data-entity-uuid="10a181b6-1e10-47e2-9167-23b96628e657" src="/sites/default/files/inline-images/EnableChangeTracking.png" /></p> <p>3, Save and Publish.</p> <p> </p> <h3>Using legacy settings</h3> <p>1, Navigate to <strong>Customizations</strong>-&gt; <strong>Customize the System.</strong></p> <p>2, Select an entity, and under <strong>Data Services</strong>, select the <strong>Change Tracking</strong> checkbox.</p> <p><img alt="Dynamics 365 Dataverse Change Tracking feature" data-entity-type="file" data-entity-uuid="fa4956e3-0a5a-49fa-889e-ff475d188d55" src="/sites/default/files/inline-images/EnableChangeTracking-Legacy.png" /></p> <h3>How to custom query for Change Tracking</h3> <p>In this section, I will show you how to query the <strong>Chang Tracing</strong> data in the different scenarios and show the response data structure.</p> <p>First of all, let's take a look at <em><strong><a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.xrm.sdk.messages.retrieveentitychangesrequest?view=dataverse-sdk-latest">RetrieveEntityChangesRequest </a></strong>and <strong><a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.xrm.sdk.messages.retrieveentitychangesresponse?view=dataverse-sdk-latest">RetrieveEntityChangesResponse</a> </strong>classes<strong>.</strong></em></p> <p><strong>RetrieveEntityChangesRequest</strong></p> <p><strong>Input parameters</strong></p> <p><strong>DataVersion </strong>is very import to only retrieve the changed data.</p> <p>Set it as empty for the first time query, then return all records.</p> <p><strong><img alt="Dynamics 365 Dataverse Change Tracking feature" data-entity-type="file" data-entity-uuid="4c6dd94d-4f79-4bb0-af19-e6b078fa7fe6" src="/sites/default/files/inline-images/ChangeTrackingRequest.png" /></strong></p> <p><strong>RetrieveEntityChangesResponse</strong></p> <p><strong><img alt="Dynamics 365 Dataverse Change Tracking feature" data-entity-type="file" data-entity-uuid="ed9450a1-b23a-483b-99d3-2c76e3bfabcf" src="/sites/default/files/inline-images/RetrieveEntityChangesResponse.png" /></strong></p> <p><strong>Prerequisite</strong></p> <p>An entity enabled the <strong>Change Tracking</strong> feature.</p> <h3><strong>Create Scenario:</strong></h3> <p>1, Create a new record named <em><strong>Test1 </strong></em></p> <p><img alt="Test1" data-entity-type="file" data-entity-uuid="01f10511-406e-4d90-9cdf-c530ff5d11c4" src="/sites/default/files/inline-images/Test1.png" /></p> <p>2, Execute below C# code to query the change data.</p> <p><img alt="Request" data-entity-type="file" data-entity-uuid="0f8dd542-a226-4e68-b485-4fb518348918" src="/sites/default/files/inline-images/Request.png" /></p> <p> </p> <p>We got <strong>1</strong> record as a <strong>NeworUpdatedEntity</strong> Item.</p> <p><img alt="NeworUpdatedEntity" data-entity-type="file" data-entity-uuid="b2aae696-aa1a-4dc9-8dc2-aadcc4acd900" src="/sites/default/files/inline-images/NeworUpdatedEntity.png" /></p> <p>The message also returns a <strong>DataToken </strong>that we can use in the <strong>RetrieveEntityChangesRequest</strong>, so that when we execute this request the next time, it returns data for those changes that occurred since the last execution of the request.</p> <p><img alt="DataToken" data-entity-type="file" data-entity-uuid="ceeecc48-d047-4204-bb10-57cae24996b8" src="/sites/default/files/inline-images/DataToken.png" /></p> <p>If retrieve again with the <strong>DataToken </strong>value, then no records were retrieved.</p> <p><img alt="NoChanges" data-entity-type="file" data-entity-uuid="95c876cc-87b0-4602-a412-f8af7f0e28f2" src="/sites/default/files/inline-images/NoChanges.png" /></p> <p> </p> <h3><strong>Update Scenario:</strong></h3> <p>update the Name to <strong>Test1-a.</strong></p> <p> <img alt="Test1-a" data-entity-type="file" data-entity-uuid="579e692c-db39-41a9-bfdf-e0e7b85dc96e" src="/sites/default/files/inline-images/Test1-a.png" /></p> <p>then execute the query with the previous <strong>DataVersion</strong>.</p> <p>Only <strong>1</strong> record was retrieved, and all the columns responded as the result.</p> <p><img alt="Only 1 record was retrieved" data-entity-type="file" data-entity-uuid="ba060b01-63c8-471c-87e5-2973c2c40fef" src="/sites/default/files/inline-images/Only%201%20record%20was%20retrieved.png" /></p> <p> </p> <h3>Delete Scenario</h3> <p>Delete the previous record <strong>Test1-a </strong>and create a new record <strong>Test2.</strong></p> <p>Execute the query code with the previous <strong>DataVersion</strong></p> <p><strong>2</strong> items were retrieved, 1 is <strong>NewOrUpdatedEntity</strong>, and 1 for <strong>RemovedItem</strong>.</p> <p><img alt="Dynamics 365 Dataverse Change Tracking feature" data-entity-type="file" data-entity-uuid="48f7397f-7375-4ca8-aed6-16a184b80a42" src="/sites/default/files/inline-images/2%20items%20were%20retrieved.png" /></p> <h3>Multiple updates within the same record.</h3> <p>If we change the Name field from <strong>Test2</strong> to <strong>Test2-1</strong>, then update to <strong>Test2-1-1</strong>.</p> <p>The last change was responded to as the result.</p> <p><img alt="The last change was responded" data-entity-type="file" data-entity-uuid="233141d0-cecf-4b78-acf8-6bdaf40371e8" src="/sites/default/files/inline-images/The%20last%20change%20was%20responded.png" /></p> <p><strong>END</strong></p> <p>Hope this will help.</p> <p>Thanks for reading.</p> <p>Regards</p> <h2 class="title">Dynamics 365 Dataverse Change Tracking feature</h2> </div> </div> <div class="field field--name-field-image field--type-image field--label-above"> <div class="field__label">Image</div> <div class="images-container clearfix"> <div class="image-preview clearfix"> <div class="image-wrapper clearfix"> <div class="field field--name-field-image field--type-image field--label-above field__item">/sites/default/files/2023-03/dataverse_cdc.png</div> </div> </div> </div> </div> Mon, 20 Feb 2023 09:11:34 +0000 Guowei 624 at https://dynamics-chronicles.com API Data Manipulation Programming Tutorial https://dynamics-chronicles.com/article/dynamics-365-dataverse-change-tracking-feature#comments