Submit Single Salesforce Object Pipeline Step always creating Contacts

For the last few weeks, I've had the opportunity to work with Sitecore Connect for Salesforce. It is based on the Data Exchange Framework and allows for, well, data exchange between Salesforce and Sitecore.

After installing the module there are quite a few pipelines ready to use such as

  • Salesforce Campaigns to xConnect Sync
  • Salesforce Contacts to xConnect Sync
  • xConnect Contacts to Salesforce Sync

and a few more. To create those, Sitecore developers had to create a number of pipeline steps which can be used in your own pipelines. This is where, in my opinion, the connector shines the most as you are able to start creating and reading Salesforce entities in just a couple minutes.

However, I did find one issue with the out-of-the-box pipeline steps, particularly with Submit Single Salesforce Object Pipeline Step. The premise of the step is relatively simple. You specify the Salesforce endpoint, the location of the object you want to submit and the name of the object in Salesforce (the other two fields are optional):

Seems easy, right? However, when trying to create any object other than Contact, you will encounter the following error:

---- Error duting creating. (object name: account) (pipeline: Process Account, pipeline step: Submit Account to Salesforce, pipeline step identifier: 015a34ec-d965-4c41-99b2-0433f323760a)
No such column 'NumberOfChildren__c' on sobject of type Contact

Notice anything weird? I intended to create an Account object and that's what the first line suggests. The second line is the actual error response from Salesforce and it mentions a Contact object instead. Interesting.

I opened the Sitecore.DataExchange.Providers.Salesforce assembly in dotPeek and located the pipeline step's processor - Sitecore.DataExchange.Providers.Salesforce.SubmitObjects.SubmitSingleObjectStepProcessor. The culprit was located in the CreateObject method:

protected virtual void CreateObject(ForceClient client, SObject sobject, string objectName)
{
    RepositoryObjectStatusSettings repositoryObjectSettings = this.GetRepositoryObjectSettings();
    this.Log(new Action<string>(this.Logger.Debug), this.PipelineContext, "Trying to update salesforce object.");
    try
    {
        SuccessResponse result = client.CreateAsync("contact", (object) sobject).Result;
        if (result.Success)
        {
            repositoryObjectSettings.Status = RepositoryObjectStatus.Exists;
            this.Log(new Action<string>(this.Logger.Info), this.PipelineContext, "Salesforce object was successfuly created. (object name: " + objectName + ")");
            this.SetResultObjectToResultLocation(this.CreateResultObject(sobject, result.Id));
        }
        else
            this.Log(new Action<string>(this.Logger.Error), this.PipelineContext, "Error during creating. (object name: " + objectName + ")");
    }
    catch (Exception ex)
    {
        this.LogException(ex, new Action<string>(this.Logger.Error), this.PipelineContext, "Error duting creating. (object name: " + objectName + ")");
        this.PipelineContext.CriticalError = true;
    }
}

Notice how the method takes an objectName parameter, but then in line #7, instead of the parameter, a hard-coded string "contact" is used.

Once the problem has been identified, the fix was easy. I created a processor inheriting the original one and overrode the CreateObject function, which utilised the objectName parameter in the CreateAsync call:

[RequiredPipelineStepPlugins(typeof(ObjectLocationSettings), typeof(SubmitObjectSettings))]
[RequiredPipelineContextPlugins(typeof(RepositoryObjectStatusSettings))]
[RequiredEndpointPlugins(typeof(AuthenticationClientSettings))]
public class SubmitSingleObjectStepProcessor : Sitecore.DataExchange.Providers.Salesforce.SubmitObjects.SubmitSingleObjectStepProcessor
{
    protected override void CreateObject(ForceClient client, SObject sobject, string objectName)
    {
        var repositoryObjectSettings = GetRepositoryObjectSettings();
        Log(Logger.Debug, PipelineContext, "Trying to update salesforce object.");
        try
        {
            var result = client.CreateAsync(objectName, sobject).Result;
            if (result.Success)
            {
                repositoryObjectSettings.Status = RepositoryObjectStatus.Exists;
                Log(Logger.Info, PipelineContext, $"Salesforce object was successfuly created. (object name: {objectName})");
                SetResultObjectToResultLocation(CreateResultObject(sobject, result.Id));
            }
            else
            {
                Log(Logger.Error, PipelineContext, $"Error during creating. (object name: {objectName})");
            }
        }
        catch (Exception ex)
        {
            LogException(ex, Logger.Error, PipelineContext, $"Error duting creating. (object name: {objectName})");
            PipelineContext.CriticalError = true;
        }
    }
}

To make Sitecore use the new class I updated the Processor Type field on the Standard Values item of the pipeline step (/sitecore/templates/Data Exchange/Providers/Salesforce/Pipeline Steps/Submit Single Salesforce Object Pipeline Step/__Standard Values):

And that's all 😊

Cover photo by Daniel von Appen on Unsplash