Amey Holden

View Original

Lookups and Owners: Spreadsheet or CSV into Dynamics 365 JSON

Following on from my previous post ‘Convert a spreadsheet or CSV file into a Marketing List in Dynamics 365 - Strings’, this post will expand the functionality to also be able to populate Lookup values, rather than just strings. To keep this as succinct as possible - if it’s blurry - I covered it in the previous post.

This is probably not the most efficient way to do this, I use a lot of compose steps that could be trimmed out but it makes it easier to read/build out/understand this way (I hope!).

A new array for headers that need transforming into lookups

LookupHeadersArray - Initialize Variable - Array

Populate LookupHeadersArray

Roll on down to your ForEachHeader loop. We are adding the steps to check the Attribute type (which we already pulled back in the Azure HTTP call for metadata earlier), then if the field is a Lookup or an Owner attribute - we will append some details to the LookupHeadersArray for later.

AttributeType - Compose

first(body('Filter_array'))?['AttributeType']

IsLookupOrOwner - Condition Control

outputs('AttributeType')

is equal to Lookup OR Owner

If Yes

AppendLookupHeaderARRAY - Append to array variable - LookupHeaderArray

{
  "originalHeaderName": "@{first(body('Filter_array'))?['LogicalName']}",
  "newHeaderName": "@{concat(first(body('Filter_array'))?['LogicalName'],'@odata.bind')}"
}

Check for Lookup & Owner values in JSON

Roll on down to your UpdateOrCreateRecord-AddToMarketingList loop. We are adding the steps to catch the current item in a compose step (you will see why later!), then check if there are any Lookups we need to fix, by checking in the LookupHeadersArray.

ITEM - Compose

item()

HasLookups - Condition Control

empty(variables('LookupHeadersArray'))

is equal to false

If Yes

UpdateLookups - Apply to each

variables('LookupHeadersArray')

Which table does the Lookup value come from?

To populate a Lookup or Owner via JSON in Dataverse the format has to look like this:

"ownerid@odata.bind":"/systemusers(1beb22c7-83ef-ec11-bb3d-000d3a1c66d8)"

Currently it looks something like this:

"ownerid":"AmeyHolden"

So we have got some data munching/string mashing to do!

This set of actions are all going to be inside the ‘UpdateLookups’ loop, looking at how we identify which table the Lookup value comes from (aka the Target), for example the Parent Contact for a lead, comes back from the Contact table

OriginalHeaderName - Compose

items('UpdateLookups')?['originalHeaderName']

OriginalHeaderValue - Compose (note here the magic wizardry to be able to dynamically refer to an objects properties!)

outputs('ITEM')?[outputs('OriginalHeaderName')]

GetTarget - Invoke an HTTP request with Azure AD

Method: GET

https://@{outputs('ODataURL')}/api/data/v9.2/EntityDefinitions(LogicalName='@{toLower(triggerBody()['text'])}')/Attributes(LogicalName='@{outputs('OriginalHeaderName')}')/Microsoft.Dynamics.CRM.LookupAttributeMetadata?$select=Targets

TargetName - Compose (there will only ever be one Target value for a regular lookup)

first(body('GetTarget')?['Targets'])

Dynamically formulate the search query

Stay inside the ‘UpdateLookups’ loop. Get the metadata we need to be able to Dynamically formulate the search query for the record, using the Primary name field and ensuring the record is active, or the owner is enabled.

GetTargetDetails - Invoke an HTTP request with Azure AD

Method: GET

https://@{outputs('ODataURL')}/api/data/v9.2/EntityDefinitions(LogicalName='@{outputs('TargetName')}')?$select=EntitySetName,PrimaryNameAttribute,PrimaryIdAttribute

FindRecordViaEntiySetName - List rows with Dataverse

TableName

concat(first(body('GetTarget')?['Targets']),'s')

FilterRows

@{body('GetTargetDetails')?['PrimaryNameAttribute']} eq '@{outputs('OriginalHeaderValue')}'@{if(equals(outputs('TargetName'), 'systemuser'), ' and isdisabled eq false', ' and statecode eq 0')}

Update or remove the lookup value

Staying within the ‘UpdateLookups’ loop again. IF we have successfully identified a row from the search above, we can update the JSON to the structure required ("ownerid@odata.bind":"/systemusers(1beb22c7-83ef-ec11-bb3d-000d3a1c66d8)") or we remove the item from the JSON to ensure the whole create step does not fail.

RecordFound - Condition Control

empty(body('FindRecordViaEntiySetName')?['value'])

is equal to false

If Yes

NewItem - Compose

"@{items('UpdateLookups')?['newHeaderName']}":"/@{body('GetTargetDetails')?['EntitySetName']}(@{first(outputs('FindRecordViaEntiySetName')?['body/value'])?[body('GetTargetDetails')?['PrimaryIdAttribute']]})"

OriginalItem - Compose

"@{outputs('OriginalHeaderName')}":"@{outputs('OriginalHeaderValue')}"

ReplaceOriginalWithNew - Compose

replace(string(variables('CreateContactOrLead')),outputs('OriginalItem'),outputs('NewItem'))

ReplaceLookupInCreateContactOrLead - Set Variable CreateContactOrLead

json(outputs('ReplaceOriginalWithNew'))

If No

RemoveLookupFromObject - Compose

removeProperty(variables('CreateContactOrLead'), outputs('OriginalHeaderName'))

RemoveLookupInCreateContactOrLead - Set Variable CreateContactOrLead

outputs('RemoveLookupFromObject')

I‘m Done

This then flows nicely back into the original flow with the updated variable CreateContactOrLead ready for its next steps. If you made it this far without saying ‘what the f*ck’ at least once it will be a miracle. The more I write of this blog, the more I see it as a lesson of when to stop digging and climb out your rabbit hole before it’s too late. Thanks for listening/reading/entertaining my madness

P.S Assumptions/Limitations

  • Polymorphic lookups will only search on their first alphabetic table e.g. owner (but you could easily fix this up in the flow)

  • Lookup search is by Primary Name only, if multiple are found with the same name, the first one returned will be chosen

  • If a lookup value cannot be found by primary name it will be skipped and not captured as part of the creation (but the creation will not fail because of it)

  • It will totally bomb out when the table multiple name is ‘ies’ rather than ‘s’ e.g. Opportunities (future Amey’s problem)