D365 Display deeply related information on a form in subgrid - dynamics-365

Have a requirement to show list of Unterweisung (trainings) titles in Contact's form as a subgrid. Please, look at:
Is there a way to display those?
I have tried to append fetchXML to form grid vie JS, but no success there by two reasons:
1) FetchXML is not valid, because of twin-linked entity search, i suppose. If it is not supported, the whole approach is wrong.
2) Even if leaving filtering only for one entity, d365 drops an error Invalid FetchXML on opening Contact form. FetchXML validators are stating opposite.
Estimated full fetchXML (not valid):
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true">
<entity name="new_schulungstyp">
<attribute name="new_name" />
<attribute name="createdon" />
<attribute name="new_schulungstypid" />
<attribute name="new_typ" />
<attribute name="new_intervall" />
<order attribute="createdon" descending="false" />
<filter type="and">
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="new_new_schulungstyp_new_azttigkeit" from="new_schulungstypid" to="new_schulungstypid" visible="false" intersect="true">
<link-entity name="new_azttigkeit" from="new_azttigkeitid" to="new_azttigkeitid" alias="aa">
<link-entity name="new_new_profil_new_azttigkeit" from="new_azttigkeitid" to="new_azttigkeitid" visible="false" intersect="true">
<link-entity name="new_profil" from="new_profilid" to="new_profilid" alias="ab">
<link-entity name="new_contact_new_profil" from="new_profilid" to="new_profilid" visible="false" intersect="true">
<link-entity name="contact" from="contactid" to="contactid" alias="ac">
<filter type="or">
<condition attribute="contactid" operator="eq" value="currentContactIdInsertedViaJs" />
</filter>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
<link-entity name="new_new_betriebsmittel_new_schulungstyp" from="new_schulungstypid" to="new_schulungstypid" visible="false" intersect="true">
<link-entity name="new_betriebsmittel" from="new_betriebsmittelid" to="new_betriebsmittelid" alias="ad">
<link-entity name="new_new_betriebsmittel_new_profil" from="new_betriebsmittelid" to="new_betriebsmittelid" visible="false" intersect="true">
<link-entity name="new_profil" from="new_profilid" to="new_profilid" alias="ae">
<link-entity name="new_contact_new_profil" from="new_profilid" to="new_profilid" visible="false" intersect="true">
<link-entity name="contact" from="contactid" to="contactid" alias="af">
<filter type="or">
<condition attribute="contactid" operator="eq" value="currentContactIdInsertedViaJs" />
</filter>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
</link-entity>
</entity>
</fetch>
JS code with single entity filter:
function filterTrainings(executionContext) {
var formContext = executionContext.getFormContext();
var guid = Xrm.Page.data.entity.getId();
var fullname = formContext.getAttribute("fullname").getValue();
//var fullName = formContext.getAttribute("fullname_d").getValue();
var fetchXML = "<fetch>";
fetchXML+= "<entity name='new_schulungstyp'>";
fetchXML+= "<attribute name='new_name' />";
fetchXML+= "<attribute name='createdon' />";
fetchXML+= "<attribute name='new_schulungstypid' />";
fetchXML+= "<attribute name='new_typ' />";
fetchXML+= "<attribute name='new_intervall' />";
fetchXML+= "<order attribute='createdon' descending='false' />";
fetchXML+= "<filter type='and'>";
fetchXML+= "<condition attribute='statecode' operator='eq' value='0' />";
fetchXML+= "</filter>";
fetchXML+= "<link-entity name='new_new_schulungstyp_new_azttigkeit' from='new_schulungstypid' to='new_schulungstypid' visible='false' intersect='true'>";
fetchXML+= "<link-entity name='new_azttigkeit' from='new_azttigkeitid' to='new_azttigkeitid' alias='aj'>";
fetchXML+= "<link-entity name='new_new_profil_new_azttigkeit' from='new_azttigkeitid' to='new_azttigkeitid' visible='false' intersect='true'>";
fetchXML+= "<link-entity name='new_profil' from='new_profilid' to='new_profilid' alias='ak'>";
fetchXML+= "<link-entity name='new_contact_new_profil' from='new_profilid' to='new_profilid' visible='false' intersect='true'>";
fetchXML+= "<link-entity name='contact' from='contactid' to='contactid' alias='al'>";
fetchXML+= "<filter type='and'>";
fetchXML+= "<condition attribute='contactid' operator='eq' value='"+guid+"' />";
fetchXML+= "</filter>";
fetchXML+= "</link-entity>";
fetchXML+= "</link-entity>";
fetchXML+= "</link-entity>";
fetchXML+= "</link-entity>";
fetchXML+= "</link-entity>";
fetchXML+= "</link-entity>";
fetchXML+= "</entity>";
fetchXML+= "</fetch>";
var grid = formContext.getControl("relatedTrainings");
if (grid == null) {
setTimeout(function () {
filterTrainings(executionContext);
}, 2000);
return;
} else {
grid.getGrid().setParameter("fetchXml", filterTrainings); //set the fetch xml to the sub grid
console.log("refresh............")
}
}

Managed to figure that one out. I am getting all required ID's that should be displayed in subgrid by subsequently sending get requests. Most important here is to manage async nature of promises. Then I build a filter that forces subgrid to show only entities, that are in related ID collection
Posting my solution:
function filterTrainings(executionContext) {
var formContext = executionContext.getFormContext();
var trainigsControl = formContext.getControl("relatedTrainings").getGrid();
var guid = Xrm.Page.data.entity.getId();
console.log(guid);
var relatedProfiles = [];
var relatedTatigkeiten = [];
var relatedBetriebsmittelGruppen = [];
var unterweisungTypen = [];
var xml;
GetRelatedProfiles()
.then(() => GetRelatedTatigkeiten().then(GetTatigkeitenRelatedUnterweisungTypen).then(() => GetRelatedBetriebsmittel().then(GetBetriebsmittelRelatedUnterweisungTypen).then(buildFetchXMLAndUpdateGrid)));
function buildFetchXMLAndUpdateGrid() {
getUniques(unterweisungTypen);
var values = unterweisungTypen.map(val => `<value>${val}</value>`);
console.log(values);
xml = `<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
<entity name="new_schulungstyp">
<attribute name="new_name" />
<attribute name="createdon" />
<attribute name="new_schulungstypid" />
<attribute name="new_typ" />
<attribute name="new_intervall" />
<order attribute="createdon" descending="false" />
<filter type="and">
<condition attribute="new_schulungstypid" operator="in">${values.join('')}</condition>
</filter>
</entity>
</fetch>`;
console.log(xml);
trainigsControl.setParameter("fetchXML", xml);
trainigsControl.refresh();
return xml;
}
function GetRelatedProfiles() {
return Xrm.WebApi.online.retrieveMultipleRecords("new_contact_new_profil", "?$select=new_profilid&$filter=contactid eq " + guid).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
relatedProfiles.push(result.entities[i]["new_profilid"]);
}
},
function (error) {
console.log(error.message);
// handle error conditions
}
);
}
function getUniques(arr) {
var uniques = [];
arr.forEach(item => {
if (uniques.indexOf(item) === -1) {
uniques.push(item);
}
});
arr = uniques;
}
function GetRelatedTatigkeiten() {
return Promise.all(relatedProfiles.map(profile =>
Xrm.WebApi.online.retrieveMultipleRecords("new_new_profil_new_azttigkeit", `?$select=new_azttigkeitid&$filter=new_profilid eq ${profile}`).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
relatedTatigkeiten.push(result.entities[i]["new_azttigkeitid"]);
}
}, function (error) {
console.log(error.message);
// handle error conditions
}
)
)).then(() => getUniques(relatedTatigkeiten));
}
function GetRelatedBetriebsmittel() {
return Promise.all(relatedProfiles.map(profile =>
Xrm.WebApi.online.retrieveMultipleRecords("new_new_betriebsmittel_new_profil", "?$select=new_betriebsmittelid&$filter=new_profilid eq " + profile).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
relatedBetriebsmittelGruppen.push(result.entities[i]["new_betriebsmittelid"])
}
}, function (error) {
console.log(error.message);
// handle error conditions
}
)
)).then(() => getUniques(relatedTatigkeiten));
}
function GetBetriebsmittelRelatedUnterweisungTypen() {
return Promise.all(relatedBetriebsmittelGruppen.map(betriebsmittel =>
Xrm.WebApi.online.retrieveMultipleRecords("new_new_betriebsmittel_new_schulungstyp", "?$select=new_schulungstypid&$filter=new_betriebsmittelid eq " + betriebsmittel).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
unterweisungTypen.push(result.entities[i]["new_schulungstypid"]);
}
}, function (error) {
console.log(error.message);
// handle error conditions
}
)
)).then(() => getUniques(unterweisungTypen));
}
function GetTatigkeitenRelatedUnterweisungTypen() {
return Promise.all(relatedTatigkeiten.map(tatigkeiten =>
Xrm.WebApi.online.retrieveMultipleRecords("new_new_schulungstyp_new_azttigkeit", "?$select=new_schulungstypid&$filter=new_azttigkeitid eq " + tatigkeiten).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
unterweisungTypen.push(result.entities[i]["new_schulungstypid"]);
}
}, function (error) {
console.log(error.message);
// handle error conditions
}
))).then(() => getUniques(unterweisungTypen));
}
}

Related

Retrieving messages supported by entity type

For each of the CRUD request there's a table with supported entities. Is there any way to obtain such info from SDK/WebAPI per entity type?
Probably some metadata properties or separate requests to check?
Maybe this documentation should help you.
To verify if a message and entity combination supports execution of plug-ins using a database query, you can use the following Web API query:
{{webapiurl}}sdkmessages?$select=name
&$filter=isprivate eq false
and (name ne 'SetStateDynamicEntity'
and name ne 'RemoveRelated'
and name ne 'SetRelated' and
name ne 'Execute')
and sdkmessageid_sdkmessagefilter/any(s:s/iscustomprocessingstepallowed eq true
and s/isvisible eq true)
&$expand=sdkmessageid_sdkmessagefilter($select=primaryobjecttypecode;
$filter=iscustomprocessingstepallowed eq true and isvisible eq true)
&$orderby=name
Fetchxml version:
<fetch>
<entity name='sdkmessage' >
<attribute name='name' />
<link-entity name='sdkmessagefilter' alias='filter' to='sdkmessageid' from='sdkmessageid' link-type='inner' >
<filter type='and' >
<condition attribute='iscustomprocessingstepallowed' operator='eq' value='1' />
<condition attribute='isvisible' operator='eq' value='1' />
</filter>
<attribute name='primaryobjecttypecode' />
</link-entity>
<filter>
<condition attribute='isprivate' operator='eq' value='0' />
<condition attribute='name' operator='not-in' >
<value>SetStateDynamicEntity</value>
<value>RemoveRelated</value>
<value>SetRelated</value>
<value>Execute</value>
</condition>
</filter>
<order attribute='name' />
</entity>
</fetch>

FetchXML or condition on the attributes of two related entities

The requirements for this FetchXML statement are:
Return all 'ebs_opportunityinternshipunit' where
'createdon' is 'last-week'
and
'owner' of 'account' is 'eq-userid'
'account' is referenced by 'parentaccountid' of 'opportunity'
or
'owner' of 'account2' is 'eq-userid'
'account2' is referenced by 'mitacs_partner2' of 'opportunity'
I've tried configuring the filter with out-of-the-box tools, XRMToolbox FetchXML Builder, visiting the official CRM Community forum, and reading FetchXML documentation. Still, the best that I've been able to come up with is below:
<fetch version="1.0" mapping="logical">
<entity name="ebs_opportunityinternshipunit">
<attribute name="ebs_name" />
<attribute name="createdon" />
<attribute name="ebs_opportunityinternshipunitid" />
<order attribute="ebs_name" descending="false" />
<filter type="and">
<condition attribute="createdon" operator="last-week" />
<filter type="or">
<condition entityname="ab" attribute="ownerid" operator="eq-userid" />
<condition entityname="ac" attribute="ownerid" operator="eq-userid" />
</filter>
</filter>
<link-entity name="opportunity" from="opportunityid" to="ebs_opportunity" link-type="inner" alias="aa">
<link-entity name="account" from="accountid" to="parentaccountid" link-type="inner" alias="ab" />
<link-entity name="account" from="accountid" to="mitacs_partner2" link-type="inner" alias="ac" />
</link-entity>
</entity>
</fetch>

Invalid XML. ---&gt On A Plugin Containing FetchXML

This happens when there is &, or other invalid characters in the contract name. My question is, how do I deal with these invalid characters??!!
Microsoft.Crm.CrmException: Invalid XML. --->
System.Xml.XmlException: An error occurred while parsing EntityName.
Line 13, position 117.
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32&
charRefEndPos)
at System.Xml.XmlTextReaderImpl.ParseAttributeValueSlow(Int32 curPos, Char quoteChar, NodeData attr)
at System.Xml.XmlTextReaderImpl.ParseAttributes()
at System.Xml.XmlTextReaderImpl.ParseElement()
at System.Xml.XmlTextReaderImpl.ParseElementContent()
at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r)
at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r, LoadOptions o)
at System.Xml.Linq.XDocument.Load(XmlReader reader, LoadOptions options)
at Microsoft.Crm.Platform.Server.Utility.XmlHelper.LoadXmlInfo(String
xmlInfo)
at Microsoft.Crm.Query.EntityExpression.ExtractPlatformName(String fetchXml, XElement element).
Here is the code in questions:
//run a fetchXml query to get how many contract lines have these values
string fetchLines = #"
<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='new_yummycontractline'>
<attribute name='new_yummycontractlineid' />
<filter type='and'>
<condition attribute='new_servingtime' operator='eq' value='" + servingTime.ToString() + #"' />
<condition attribute='new_servinggroup' operator='eq' value='" + servingGroup.ToString() + #"' />
<condition attribute='new_destination' operator='eq' value='" + location.ToString() + #"' />
<condition attribute='statecode' operator='eq' value='0' />
</filter>
<link-entity name='new_yummycontract' from='new_yummycontractid' to='new_contractid' link-type='inner' alias='ad'>
<filter type='and'>
<condition attribute='new_yummycontractid' operator='eq' uiname='" + uiname + #"' uitype='new_yummycontract' value='" + contractId.ToString() + #"' />
</filter>
</link-entity>
</entity>
</fetch>";
EntityCollection results = service.RetrieveMultiple(new FetchExpression(fetchLines));
uiname and uitype are not required for your fetchXml to work. These are used for presentation and not for the query. I think these are added by the Advanced Find editor; hence the "UI"
You can rewrite that section of XML like so
<link-entity name='new_yummycontract' from='new_yummycontractid' to='new_contractid' link-type='inner' alias='ad'>
<filter type='and'>
<condition attribute='new_yummycontractid' operator='eq' value='" + contractId.ToString() + #"' />
</filter>
</link-entity>
Not sure which version of .NET you're using, but you can also use the $ special character to indicate an interpolated string which I think increases readability and reduces string concatenation
string fetchLines = #$"
<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='new_yummycontractline'>
<attribute name='new_yummycontractlineid' />
<filter type='and'>
<condition attribute='new_servingtime' operator='eq' value='{servingTime}' />
<condition attribute='new_servinggroup' operator='eq' value='{servingGroup}' />
<condition attribute='new_destination' operator='eq' value='{location}' />
<condition attribute='statecode' operator='eq' value='0' />
</filter>
<link-entity name='new_yummycontract' from='new_yummycontractid' to='new_contractid' link-type='inner' alias='ad'>
<filter type='and'>
<condition attribute='new_yummycontractid' operator='eq' value='{contractId}' />
</filter>
</link-entity>
</entity>
</fetch>";
I used webutility.htmlencode(uiname) and the problem seems to have disappeared. I guess the uiname has some invalid XML characters and this method gets rid of the problem!

Not able to fetch "Name" of lookup fields of Link-Entity (Web API + FetchXml)

I have some fetchXml that I am executing through the Dynamics CRM Web API. THe fetchXml query is constructed like so:
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
<entity name="new_someEntityA">
<attribute name="new_lookupForSomeEntityA" />
<link-entity alias="new_someEntityB" name="new_someEntityB" from="entityBId" to="entityAId" visible="false" link-type="outer">
<attribute name="new_lookupForSomeEntityB" />
</link-entity>
</entity>
</fetch>
When I send this query through the Web API, I get a response and the value for "new_lookupForSomeEntityA" includes the value (GUID) and a Formatted Value (it's name). But the response for "new_lookupForSomeEntityB" includes just the GUID and I can't find a way to get it's GUID and value. I've added a header record for:
"Prefer": "odata.include-annotations=OData.Community.Display.V1.FormattedValue"
but that appears to just get me formatted values for the primary entity and not the link entity. Is this a limitation of the Web API or am I doing something wrong? Any help would be appreciated.
Here is some code that returns formatted values for link-entity option sets with Web API + FetchXml. Tested against version 8.2 of the API:
var oDataUrl = 'https://[your_org].crm4.dynamics.com/api/data/v8.2/';
var encodedFetchXml = encodeURI(`
<fetch top="10" no-lock="true" >
<entity name="contact" >
<attribute name="fullname" alias="contactName" />
<link-entity name="incident" from="customerid" to="contactid" link-type="inner" alias="incident" >
<attribute name="caseorigincode" alias="incidentOrigin" />
</link-entity>
</entity>
</fetch>
`);
$.ajax({
type: "GET",
contentType: "application/json; charset=utf-8",
datatype: "json",
url: `${oDataUrl}contacts?fetchXml=${encodedFetchXml}`,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Accept", "application/json");
XMLHttpRequest.setRequestHeader("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");
}
}).then(function (response) {
// formatted value included in the response objects
// incidentOrigin#OData.Community.Display.V1.FormattedValue:"WhatsApp"
console.dir(response);
});
This is the data that is returned:
{
"#odata.context":"https://[your_org].crm4.dynamics.com/api/data/v8.2/$metadata#contacts(contactid)",
"value":[{
"#odata.etag": "W/\"873006\"",
"contactid": "ecfd1feb-d826-468a-bfe3-6ebd781c39f4",
"contactName": "Ada Lovelace",
"incidentOrigin#OData.Community.Display.V1.FormattedValue": "WhatsApp",
"incidentOrigin": 269420000
}]
}
It works with both inner and outer link-types.
Link-Entity attribute alias named the same as the attribute
If you make the alias for a linked-entity attribute the same as the attribute name, then the attribute is not returned at all. This problem appears to be for all link-entity attributes.
<fetch top="500" no-lock="true" >
<entity name="contact" >
<attribute name="fullname" alias="contactName" />
<link-entity name="incident" from="customerid" to="contactid" link-type="inner" alias="incident" >
<attribute name="caseorigincode" alias="caseorigincode" />
</link-entity>
</entity>
</fetch>
Leaving out the link-entity attribute alias
If you leave out the alias for a linked-entity attribute, it is returned with a horrible name:
<fetch top="500" no-lock="true" >
<entity name="contact" >
<attribute name="fullname" alias="contactName" />
<link-entity name="incident" from="customerid" to="contactid" link-type="inner" alias="incident" >
<attribute name="caseorigincode" />
</link-entity>
</entity>
</fetch>
The returned objects have a field like this:
incident_x002e_caseorigincode#OData.Community.Display.V1.FormattedValue:"WhatsApp"
Maybe the issue you experienced was resolved in version 8.2.

CRM QueryExpression with Link to Same Table / Entity

I am trying to perform the following SQL equivalent query within CRM using a QueryExpression and LinkEntity. Any ideas? I am fairly stumped. I have used the LinkEntity before but not sure how to reference or alias the same entity type.
select t1.UoMId, t1.BaseUoM, t1.UoMScheduleId, t1.Name, t1.Quantity
from UoMBase t1
inner join UoMBase t2 on t1.UoMScheduleId = t2.UoMScheduleId
where t2.UoMId = '57E59AB7-AC8F-E511-80F0-005056BE36DF';
Which will result in the following entity records:
Try to convert following to QueryExpression:
<fetch mapping="logical" version="1.0">
<entity name="UoM">
<attribute name="UoMId" />
<attribute name="BaseUoM" />
<attribute name="UoMScheduleId" />
<attribute name="Name" />
<attribute name="Quantity" />
<link-entity name="UoM" from="UoMScheduleId" to="UoMScheduleId" alias="t2" link-type="inner">
<filter>
<condition attribute="UoMId" operator="eq" value="57E59AB7-AC8F-E511-80F0-005056BE36DF" />
</filter>
</link-entity>
</entity>
</fetch>
QueryExpression query = new QueryExpression("uom");
query.ColumnSet = new ColumnSet(new[] { "uomid", "baseuom", "uomscheduleid", "name", "quantity", });
query.LinkEntities.Add(new LinkEntity("uom", "uom", "uomscheduleid", "uomscheduleid", JoinOperator.Inner) { EntityAlias = "t2" });
query.Criteria.AddCondition("t2", "uomid", ConditionOperator.Equal, guidID);
EntityCollection result = OrganizationService.RetrieveMultiple(query);

Resources