Loading...

Making CrmWebApi Entity References Suck Less For Creates and Updates

Making CrmWebApi Entity References Suck Less For Creates and Updates
For those that “grew up” on the 2011 Rest endpoint for CRM, attempting to populate Entity Reference attributes for create or update calls feels rather painful in the new CrmWebApi.
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 {
    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 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.

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 {
    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;
}
This then is all used in the prepareForOData function:
/**
* 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;
}
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:
export function create(entityCollection: string, data: any): Promise<any> {
    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);
}
And now, these two calls, will result in the same exact request made to the CrmWebApi:

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 more
.Net Dust
.Net Dust

NULL

Share post:

Related posts

PowerFx Formatting If Statement Idiosyncrasies

Disclaimer: This entire blog is an opinion, and opinions are like butts, we all have one.  This is not doctrinal truth that must be obser...

1 year ago

Separating Plugin Logic: A Guide to Testing Dataverse Plugins with IOC

I’m not a pure TDD developer.  I frequently take my best guess at a Dataverse plugin, then apply TDD until everything works.  This c...

3 years ago

How to Filter Dates in Canvas Apps Using Greater Than/Less Than Operators

Defining the ProblemRecently I was attempting to filter an on-premise SQL table by a DateTime field using a “greater than” operator, and displ...

3 years ago

Enabling or Disabling All Plugin Steps In Dataverse

If you are facing an issue with a recent bug in PowerPlatform.BuildTools version 0.0.81 that caused your plugin steps to become disabled, fret...

4 years ago

Using AutoFixture To Create Early Bound Entities

AutoFixture is an open source library that is used in testing to create objects without having to explicit set all the values.  I recentl...

4 years ago

Long Functions Are Always A Code Smell

This article is in response to fellow MVP Alex Shelga’s recent article Long functions in dataverse plugins – is it still ” code smell”?. ...

4 years ago

Handle All Your Plugin Exceptions In One Place, And Then Hide It!

In coding, one of the recommended best practices is to avoid creating duplicate code as it leads to maintenance issues. Therefore, it is advis...

6 years ago

Newsletter

Get the latest Dynamics 365 and Power Platform content in your inbox

A curated digest of community blogs, product news, videos, and podcasts — delivered without the noise.

Weekly updates Unsubscribe anytime Fresh community picks
We use your email only for the newsletter and you can unsubscribe at any time.
By subscribing, you agree to the privacy policy.