Recently we observed that our plugin registered on the Delete message of appointment on the PreValidation stage was not getting triggered when we were deleting or updating the occurrence of the Recurring Appointment.
For both of the below operations, our plugin was not getting triggered. The plugin had the logic to delete a few associated records to the appointment record.
Delete operation – This deletes all the existing appointment.
Recurrence Update –This deletes the existing appointment and creates new appointment records based on the new recurrence.
On trying out different things, we saw that the plugin was getting triggered if registered on the PreOperation.
For testing, we registered a sample plugin that throws the InvalidPluginExecutionException and saw it getting triggered in case of PreOpertaion as shown below.
The Case Handling Time widget (preview) can be added to the Case Form, giving a focused way to track case resolution efficiency. By using this feature, we can identify patterns, make informed decisions, and continuously refine processes.
To enable it to navigate to
Customer Service Admin Center >> Customer Support >> Case Settings >> Case handling time (preview)
Update Interval specifies how frequently the handling time widget is refreshed (it also updates the database). Can be between 10 to 60 minutes. The interesting point here is if 2 agents open the same case at the same time, the widget will display 20 minutes, after 10 minutes.
Next, open the form where we want to add the component, click on Get more components, and search for the Handling Time component to add it.
Configure the properties for the component –
Table – Time Trackers
View – All Time Trackers
Allow users to add time logs – True / False (enables agents to log time)
Show users only their contributed time – True / False (specifies whether an agent can only see their time or time entered by other agents also for that case)
Allow users to edit their automatic time tracked – True / False (specifies if an agent can edit automatic time tracked).
Save and publish the changes.
We can see the Handling Time widget at the bottom right corner in the minimized state on the form.
On maximizing it after some time, we can see the following details there.
Automatic Time Tracking, Manual Time Tracking, History section.
Checking My Time shows the Live time spent by that particular agent.
Clicking on the plus button allows us to add manual Time log records.
If there is a second user who opens the form, the automatic time tracking shows the values applied to that user.
We might get the below error while using LINQ to query Dataverse –
System.NotSupportedException: ‘The method ‘GroupJoin’ cannot follow the method ‘SelectMany’ or is not supported. Try writing the query in terms of supported methods or call the ‘AsEnumerable’ or ‘ToList’ method before calling unsupported methods.’
This error occurs because the GroupJoin method, which is essentially what happens during the into … syntax in LINQ, is not supported in the Dataverse/LINQ provider for Dynamics CRM. The issue is related to how the LINQ provider translates queries into FetchXML or SQL that the Dataverse understands. Specifically, nested joins or SelectMany (used by DefaultIfEmpty() in left joins) are not fully supported by the LINQ provider for Dataverse.
The GroupJoin and DefaultIfEmpty methods (used for left joins) result in queries that cannot be translated directly into FetchXML. The LINQ provider for Dataverse does not support all LINQ-to-Entities features, such as nested joins or advanced grouping logic. When you perform nested joins with DefaultIfEmpty() for left joins, the LINQ provider struggles to translate it into the underlying Dataverse query format, which is why the exception is thrown.
To fix it we can break the query into multiple steps as shown below.
However, here as we are fetching partial data into memory and combining it, it increases transfer and processing overhead and can take a long time to process based on the number of records.
The better alternative from a performance perspective would be to use FetchXML or QueryExpression here.
We might get the below error while using LINQ to query Dataverse – “System.NotSupportedException: ‘Invalid ‘where’ condition. An entity member is invoking an invalid property or method.’” Here we got the below error because we used the HasValue property.
Here the issue with HasValue arises because FetchXML doesn’t have a direct equivalent for nullable checks like HasValue in LINQ. In FetchXML, null checks are handled explicitly through conditions like neq (not equal) or eq (equal) with the null value. Therefore, instead of using HasValue, we need to manually check for null using != null or GetValueOrDefault().
We will also get the same error on below query.
Here we get this error because OriginatingLeadId is a LookUp referencing Lead table, and we are trying to access the Name property of it in the where clause, which cannot be directly translated into FetchXML. To fix it we can perform a join between the Contact and Lead table.
Let us see one more example.
The query, c.Attributes[“firstname”].ToString() is trying to access the firstname attribute and convert it to a string. However, the LINQ provider doesn’t know how to translate.ToString() into a valid FetchXML query.
To fix it we can use GetAttributeValue method.
Or doing the casting
However, we will get the same not supported error if we try to use Attributes Collection.
The error occurs because of the syntax c.Attributes[“firstname”] directly accesses the internal Attributes dictionary of the Entity object. The LINQ provider in Dataverse (Dynamics 365) cannot translate this access pattern into a FetchXML
Recently we migrated the Real-time marketing forms from our Dev environment to UAT environment using the Configuration Migration Tool. Here what we noticed was on submitting the form using the standalone page in UAT, the submissions were going back to the Dev environment instead of UAT.
This was because the data-form-api-url and data-cached-form-url property of msdynmkt_standalonehtml field of msdynmkt_marketingform record was still referencing the organization id of the Dev environment instead of UAT.
update msdynmkt_marketingform
set msdynmkt_standalonehtml = '<div data-form-id=''067fce01-b674-ef11-a671-002248e36298''
data-form-api-url=''https://public-oce.mkt.dynamics.com/api/v1.0/orgs/dd628e4e-ffd7-ed11-aecf-002248932ace/landingpageforms''
data-cached-form-url=''https://assets-oce.mkt.dynamics.com/dd628e4e-ffd7-ed11-aecf-002248932ace/digitalassets/forms/067fce01-b674-ef11-a671-002248e36298'' >
</div><script src = ''https://cxppusa1formui01cdnsa01-endpoint.azureedge.net/oce/FormLoader/FormLoader.bundle.js''></script>'
where msdynmkt_marketingformid = '067fce01-b674-ef11-a671-002248e36298'
After the update we can see submission reflecting back to the correct UAT environment.
The query we used to update all the forms with UAT’s Id
-- replace Dev Org ID with UAT Org ID in all the marketing forms
UPDATE msdynmkt_marketingform
SET msdynmkt_standalonehtml = REPLACE(msdynmkt_standalonehtml, 'f69f1cea-23d9-446f-9130-ed45ce666b28', 'dd628e4e-ffd7-ed11-aecf-002248932ace')
where msdynmkt_standalonehtml is not null
We can use the setIsValid method for validating field values in Model-driven apps. This method helps ensure that the data entered by users meets the required criteria before it’s processed or saved.
The setIsValid method is used to set the validity of a column’s value. It can mark a field as valid or invalid based on custom validation logic.
bool: A Boolean value. Set to false to mark the column value as invalid, and true to mark it as valid.
message: (Optional) A string containing the message to display when the value is invalid.
Below we are using the setIsValid method in the function that ensures that the “End Date” is earlier than or equal to the “Start Date”, else it will mark the “End Date” as invalid.
function validateDates(executionContext) {
var formContext = executionContext.getFormContext();
var startDate = formContext.getAttribute("custom_startdate");
var endDate = formContext.getAttribute("custom_enddate");
if (startDate && endDate && endDate.getValue() <= startDate.getValue()) {
endDate.setIsValid(false, "End Date must be after Start Date.");
} else {
endDate.setIsValid(true);
}
}
We have it registered in the On Change of the ‘Start Date’ and ‘End Date’ fields.
Here if we try saving the record, if the End Date is smaller than the Start Date, we will get the message specified.