Reputation: 165
I'd like to list out the Quote Lines of a Quote, while keeping the interface generic enough to be able to display other entities similarly.
In order to do this, I'm trying to make use of the PrimaryNameAttribute found in the EntityMetadata. Works great for most entities, but for QuoteDetail, the PrimaryNameAttribute is "productidname" - an attribute that doesn't actually exist as part of the QuoteDetail entity.
This field, "productidname", is also the PrimaryNameAttribute for other entities (OpportunityProduct, SalesOrderDetail), and in those cases the field is also missing.
What gives? I feel like I've searched all of Google and it doesn't seem anyone has run into this issue before, so, maybe I'm missing something simple.
Here's the QuoteDetail entity page off of MSDN that shows what I'm talking about: https://msdn.microsoft.com/en-us/library/mt607959.aspx Notice that the PrimaryNameAttribute isn't listed anywhere else on the page.
Upvotes: 0
Views: 691
Reputation: 18061
QuoteDetail (like SalesOrderDetail and OpportunityProduct) is special in that it can be either relation entity to a Catalog Product item or Write-In item and gets its "name" from either productidname
(product from catalog) or productdescription
(write-in product). You will need to JOIN the related Product to get its name.
If you are about to develop a generic solution/interface, this is a problem you need to tackle for every Lookup attribute.
If you only ever need the PrimaryNameAttribute from records connected in Lookups, you can retrieve the name from the FormattedValues
collection of your "main" entity:
string productname = quotedetail.FormattedValues["productid"];
yet better/safer:
string productname;
quotedetail.FormattedValues.TryGetValue("productid", out productname);
Handling attributes of an arbitrary entity can look like this:
foreach (var key in record.Attributes.Keys)
{
if (record.FormattedValues.ContainsKey(key))
{
string formattedvalue;
if (record.FormattedValues.TryGetValue(key, out formattedvalue))
{
Console.WriteLine(formattedvalue); // use formattedvalue string
}
continue; // skip to next field when found in formatted values
}
object attributevalue;
record.Attributes.TryGetValue(key, out attributevalue);
object actualvalue;
string actualtext = string.Empty;
// handle AliasedValue fields from JOINed/LinkEntities
if (attributevalue.GetType().Name == "AliasedValue")
{
actualvalue = ((AliasedValue)attributevalue).Value;
}
else
{
actualvalue = attributevalue;
}
switch (actualvalue.GetType().Name)
{
case "EntityReference":
actualtext = ((EntityReference)actualvalue).Name; // this will catch Lookup values not contained in FormattedValues when you just created them
break;
case "DateTime":
actualtext = string.Format("{0:dd.MM.yyyy}", ((DateTime)actualvalue).ToLocalTime()); // ... any other dateTime format you'd like
break;
case "Guid":
actualtext = string.Format("{0:D}", actualvalue); // Entity Primary key
break;
default:
actualtext = (string)actualvalue; // anything else
break;
}
Console.WriteLine(actualtext);
}
You will still have to take care of newly assigned OptionSetValue
and Money
attributes (similar to EntityReference
ones) because those would usually pulled from FormattedValues
.
Since this example rather deals with existing crm data, you need to be aware of the pitfall that your entity likely will not include all attributes, so instead of iterating .Attributes.Keys
you may want to go over a predefined collection of attribute names.
My personal strategy is usually to create a lightweight ORM to map between typed objects and CRM entities but this would not fit your requirement of a generic interface.
For cases like yours where it's either this or that attribute I put syntactical sugar to work:
string pn = qd.GetAttributeValue<string>("productdesription") ?? (qd.GetAttributeValue<EntityReference>("productid") ?? new EntityReference { Name = string.Empty }).Name;
Try to get the Write-In product name; if it is null, try to get the Lookup name and if this is null, get an empty string from a fake EntityReference.
This allows rather friction-less coding and solves the "either this or that attribute" nicely.
Upvotes: 1