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
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...
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...
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...
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...
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...
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”?. ...
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...