I am attempting to write a generic method that given any Dynamics 365 entity name and a set of IDs (GUIDS) will respond with a set of entity records matching that enumerated set. My frustration is that there doesn't seem to be an efficient way to get the API to simply use the "primary ID key" without getting it first from metadata, another (seemingly unnecessary) round trip.
Consider the following (Hacked) method:
public EntityCollection HackedFetchEntityRecordsById(IOrganizationService orgSvc, string entityName, IEnumerable<Guid> primaryEntityAttributeIds)
{
// Defacto HACK for getting primary entity attribute
string primaryEntityAttribute = $"{entityName}id";
StringBuilder sb = new StringBuilder();
foreach (Guid guid in primaryEntityAttributeIds)
{
sb.AppendLine($@"<value>{guid}</value>");
}
string fetchXml = $@"
<fetch mapping='logical'>
<entity name='{entityName}'>
<no-attrs />
<filter>
<condition attribute='{primaryEntityAttribute}' operator='in'>
{sb}
</condition>
</filter>
</entity>
</fetch> ";
return orgSvc.RetrieveMultiple(new FetchExpression(fetchXml));
}
Note that here I am simply using a defacto standard I have observed, in that it seems Microsoft has chosen to name primary id attributes on entities with the entity name followed by the string "id". This is clearly unsafe and a terrible way to do this.
I can do it the "right" way but it is inefficient:
public EntityCollection InefficientFetchEntityRecordsById(IOrganizationService orgSvc, string entityName, IEnumerable<Guid> primaryEntityAttributeIds)
{
// "Correct" but inefficient way of getting primary entity attribute
string primaryEntityAttribute = ((RetrieveEntityResponse) orgSvc.Execute(new RetrieveEntityRequest
{
LogicalName = entityName
})).EntityMetadata.PrimaryIdAttribute;
StringBuilder sb = new StringBuilder();
foreach (Guid guid in primaryEntityAttributeIds)
{
sb.AppendLine($@"<value>{guid}</value>");
}
string fetchXml = $@"
<fetch mapping='logical'>
<entity name='{entityName}'>
<no-attrs />
<filter>
<condition attribute='{primaryEntityAttribute}' operator='in'>
{sb}
</condition>
</filter>
</entity>
</fetch> ";
return orgSvc.RetrieveMultiple(new FetchExpression(fetchXml));
}
Note that in this case, I need to make a separate service call (with all the overhead that entails) to get the entity metadata in order to discern what the primary attribute is. Yuck.
I would like to do something like the following (fantasy/not working) method:
public EntityCollection FantasyFetchEntityRecordsById(IOrganizationService orgSvc, string entityName, IEnumerable<Guid> primaryEntityAttributeIds)
{
StringBuilder sb = new StringBuilder();
foreach (Guid guid in primaryEntityAttributeIds)
{
sb.AppendLine($@"<value>{guid}</value>");
}
// ILLEGAL XML - made up element "primaryEntityAttribute"
string fetchXml = $@"
<fetch mapping='logical'>
<entity name='{entityName}'>
<no-attrs />
<filter>
<primaryEntityAttribute operator='in'>
{sb}
</primaryEntityAttribute>
</filter>
</entity>
</fetch> ";
return orgSvc.RetrieveMultiple(new FetchExpression(fetchXml));
}
I would be very happy to use some other implementation of QueryBase in the RetrieveMultiple service.