Making CrmWebApi Entity References Suck Less For Creates and Updates
account["[email protected]"] = "/contacts(E15C03BA-10EC-E511-80E2-C4346BAD87C8)";Was it “"@odata.bind” or “@bind.odata”? Was it a forward slash or backward slash? Did the Guid have curly braces?
Yes it’s a small pain, but it is bigger if you normally use field accessors (“entity.field” rather than array accessors: “entity[‘field’]”) because "[email protected]" isn't a valid field name. It’s probably because of my C# background, but I prefer not to use the object array accessor method when possible. So the question is, how to make this syntax better and help me remember it.
On my current project I use David Yack’s CRMWebAPI. It’s simple, and uses standard Promises, so no need for a new library, just polyfill Promises (if you’re using IE 11) and you’re all set. The calls are wrapped by a custom TypeScript library (CrmWebApiLib) to allow for some custom changes, of which, this implementation is one. First, the library defines an Entity Reference class (*Note, this is TypeScript, get it, use it, love it)
export class EntityReference implements ODataFormattable {The class has two public properties, “collectionName” and “id”, and implements the two functions of the ODataFormattable interface, “toODataFromat” and “getODataPropertyName”. The toODataFormat adds the forward slash and formats the guid correctly, and the getODataPropertyName appends the “@data.bind” to the property name parameter.
constructor(public collectionName: string, public id: string) { }
toODataFormat = (): string => {
return `/${this.collectionName}(${CrmWebApiLib.removeCurlyBraces(this.id)})`;
}
getODataPropertyName = (propertyName: string): string => {
return `${propertyName}@odata.bind`;
}
}
The ODataFormattable interface just defines the two functions. Then there is also a User Defined Type Guard to determine if any given object implements the ODataFormattable interface:
export interface ODataFormattable {This then is all used in the prepareForOData function:
toODataFormat(): string;
getODataPropertyName(propertyName: string): string;
}
export function isODataFormattable(arg: any): arg is ODataFormattable {
const formattable = arg as ODataFormattable;
return formattable && formattable.toODataFormat !== undefined && formattable.getODataPropertyName !== undefined;
}
/**It creates a new object, and basically loops through all properties of the data object, copying it over to the new object. If the value of the property is a ODataFormatable, it will update the value as well as the property name. There is then a recursive map call to handle arrays as well (think party lists). prepareForOData is then called from within the create and update methods:
* Loops through properties, searching for any ODataFormattable properties or arrays with ODataFormattable, and updates the format to be OData Friendly
* @param data
*/
function prepareForOData(data: any): any {
const oData = {};
for (const propName in data) {
if (!data.hasOwnProperty(propName)) {
continue;
}
const value = data[propName];
if (isODataFormattable(value)) {
oData[value.getODataPropertyName(propName)] = value.toODataFormat();
} else if (value instanceof Array) {
oData[propName] = value.map(prepareForOData);
} else {
oData[propName] = value;
}
}
return oData;
}
export function create(entityCollection: string, data: any): Promise<any> {And now, these two calls, will result in the same exact request made to the CrmWebApi:
return instance().Create(entityCollection, prepareForOData(data));
}
export function update(entityCollection: string, key: string, data: any, upsert?: boolean): Promise<any> {
if (key.indexOf("{") >= 0 || key.indexOf("}") >= 0) {
key = CrmWebApiLib.removeCurlyBraces(key);
}
return instance().Update(entityCollection, key, prepareForOData(data), upsert);
}
No Bueno
const note = {};
note["notetext"] = CommonLib.getValue(fields.description);
note["[email protected]"] = `/allgnt_locations(${CommonLib.getSelectedLookupId(fields.location)})`;
CrmWebApiLib.create("annotations", note);
Muy Bueno
const note = {
notetext: CommonLib.getValue(fields.description),
objectid_allgent_location: new CrmWebApiLib.EntityReference("allgnt_locations", CommonLib.getSelectedLookupId(fields.location))
};
CrmWebApiLib.create("annotations", note);
Published on:
Learn moreRelated posts
50 Real Dynamics 365 CE Interview Questions Asked by Top Companies with Expert Answers (2026 Edition)
Introduction Microsoft Dynamics 365 Customer Engagement (CE), formerly known as Dynamics CRM, has become one of the most sought-after enterpri...
Model Context Protocol in Microsoft Dynamics 365 CE/CRM: What MCP Means for Sales and Customer Service AI Agents
An AI agent can summarize an account and still miss the detail that matters most. A seller may receive a recommendation without recent service...
Tips to Export Data To Data Lake From MS Dynamics 365 CE And Use It In Power BI.
Integrating Dynamics 365 with Azure Data Lake allows you to continuously sync operational data for cost-effective big data analytics, AI workl...
Copilot Cowork and Dynamics 365 Customer Engagement: What It Means for Sales and Service Teams
Microsoft’s Copilot Cowork update matters because it moves AI closer to daily sales and service work inside Dynamics 365. Sellers, service man...
Beyond Attribution: Pipeline Intelligence from Marketing Activities with Dynamics 365 CE/CRM and Power BI
At New Dynamic, a marketing reporting project evolved into something much larger. What began as an effort to better understand marketing activ...
Why Solution Structure Determines Whether Microsoft Dynamics 365 Customer Engagement Environments Scale Successfully
Many Microsoft Dynamics 365 Customer Engagement environments do not become difficult overnight. The friction builds gradually. Deployments req...
The Revenue Leak Your Dynamics CRM Cannot See
Key Takeaways: 1. The biggest revenue loss in field sales is invisible. It never shows up in a report; it's the nearby visit a rep never knew ...
AI Agents in Microsoft Power Platform: Where Custom Agentic CRM Fits in Dynamics 365 Customer Engagement
In many CRM planning conversations right now, AI agent discussions are starting before organizations have fully aligned governance, integratio...
Business Process Flows in Dynamics 365 CE
Let’s look back at an oldie but a goodie in Dynamics 365 CE/CRM: Business Process Flows! These are designed to standardize how records m...
20 Most Commonly Used JavaScript Scenarios with Sample Code Snippets in Form Script – Dataverse / Dynamics 365 CE
JavaScript plays a critical role in Microsoft Dataverse and Dynamics 365 Customer Engagement (CE) applications. While Power Automate and Busin...