Problem/Motivation

I found a difference in the upsert logic between 7.x-3.x and 8.x-3.x, and I think the 7.x-3.x logic is better. In 7.x-3.x I can change the value of an upsert key field in Drupal and see that change reflected in the related Salesforce object. When I do this in 8.x-3.x I get a new/duplicate object up on Salesforce and the original Salesforce object is disconnected from Drupal. It comes down to this:

In 7.x-3.x upsert is only attempted when our Drupal entity isn't already linked/mapped to a Salesforce object. Drupal entities that are already mapped are updated instead of upserted. This allows updates to the Drupal entity's upsert key field value to flow through to the Salesforce object.

In 8.x-3.x upsert is always used if an upsert key field is defined, period. This means when a mapped Drupal entity's upsert key field value is changed, a new object is created up on Salesforce. Following this, the mapped object in Drupal is updated to hold this new Salesforce ID, so our existing Drupal entity is re-mapped to this newly-created Salesforce object and the originally-mapped salesforce object is essentially disconnected from Drupal. Messy to say the least.

Proposed resolution

Change the push logic back to what we had in 7.x-3.x: If a mapped object with a Salesforce ID already exists for the entity, perform an objectUpdate(). If not, and an upsert key is defined, perform an objectUpsert(). Failing all of that, perform an objectCreate().

In code I'm essentially proposing we change this (modules/salesforce_mapping/src/Entity/MappedObject.php):

if ($mapping->hasKey()) {
  $action = 'upsert';
  $result = $this->client()->objectUpsert(
    $mapping->getSalesforceObjectType(),
    $mapping->getKeyField(),
    $mapping->getKeyValue($drupal_entity),
    $params->getParams()
  );
}
elseif ($this->sfid()) {
  $action = 'update';
  $result = $this->client()->objectUpdate(
    $mapping->getSalesforceObjectType(),
    $this->sfid(),
    $params->getParams()
  );
}
else {
  $action = 'create';
  $result = $this->client()->objectCreate(
    $mapping->getSalesforceObjectType(),
    $params->getParams()
  );
}

To this:

if ($this->sfid()) {
  $action = 'update';
  $result = $this->client()->objectUpdate(
    $mapping->getSalesforceObjectType(),
    $this->sfid(),
    $params->getParams()
  );
}
elseif ($mapping->hasKey()) {
  $action = 'upsert';
  $result = $this->client()->objectUpsert(
    $mapping->getSalesforceObjectType(),
    $mapping->getKeyField(),
    $mapping->getKeyValue($drupal_entity),
    $params->getParams()
  );
}
else {
  $action = 'create';
  $result = $this->client()->objectCreate(
    $mapping->getSalesforceObjectType(),
    $params->getParams()
  );
}

Remaining tasks

Patch & review.

User interface changes

None.

API changes

None.

Data model changes

None.

Comments

chrisolof created an issue. See original summary.

chrisolof’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new1.96 KB

Patch attached.

Status: Needs review » Needs work

The last submitted patch, 2: salesforce-upsert-logic-correction-3001593-2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

aaronbauman’s picture

Following this, the mapped object in Drupal is updated to hold this new Salesforce ID, so our existing Drupal entity is re-mapped to this newly-created Salesforce object and the originally-mapped salesforce object is essentially disconnected from Drupal. Messy to say the least.

The flip side of this is also messy, because it opens the possibililty that we can break the uniqueness of external key values in Salesforce.

I'm curious, what is your use case for changing the upsert key value?
I don't have a specific use case in mind where "always use upsert" is better or worse, i'm just wondering if there is a reason to maintain some support for both behaviors.

chrisolof’s picture

Use case: Drupal users mapped to SF objects. Upsert key is email. Using upsert removes any potential for record duplication when new users register on the website. Our upsert key field is marked unique on SF so the API won't permit a push that would break uniqueness (nor would Drupal allow a user to change his/her email to one in use by another user). Ideally a user could change his/her email address on the website and we could see that change reflected in the corresponding object on Salesforce (D7 & patch logic). With the new D8 push logic we're finding our use-case is no longer supported.

Idea: What if we were to add a new setting in the "Upsert key" fieldset - something to the effect of: "Restrict upsert to unmapped entities". Maybe with help-text of "Restricting upsert to unmapped entities can resolve mapping issues in cases where the upsert key's value is changeable." And then if that's checked we go with the D7 logic on push, which is essentially restricting upsert to unmapped entities.

chrisolof’s picture

Status: Needs work » Needs review
StatusFileSize
new3.1 KB
new957 bytes

Attached is patch #2 with MappedObjectTest updated per the new logic.

aaronbauman’s picture

The main use case I can think of to prefer upsert is compatibility across different environments.
e.g. if i'm using UUID as upsert value, i can be sure that the same entity will be matched whether its been migrated to / from a different drupal site, or whether i'm switching between salesforce sandbox and prod.

Idea: What if we were to add a new setting in the "Upsert key" fieldset - something to the effect of: "Restrict upsert to unmapped entities". Maybe with help-text of "Restricting upsert to unmapped entities can resolve mapping issues in cases where the upsert key's value is changeable." And then if that's checked we go with the D7 logic on push, which is essentially restricting upsert to unmapped entities.

Good idea, so that we preserve existing behavior in case anyone is relying on it.

In terms of naming, maybe "Always use upsert" makes more sense, semantically, for the checkbox label?

aaronbauman’s picture

Assigned: Unassigned » aaronbauman

gonna take a stab at this

aaronbauman’s picture

Assigned: aaronbauman » Unassigned
StatusFileSize
new8.78 KB
new7.92 KB

iteration on #6, with the addition of:
- add "always upsert" mapping field
- add salesforce_mapping_update_8003 to apply this value to all existing mappings with upsert key, so that this is a non-breaking change

  • aaronbauman committed f1f3e3b on 8.x-3.x authored by chrisolof
    Issue #3001593 by chrisolof, aaronbauman: Changing an upsert key field...
aaronbauman’s picture

Status: Needs review » Fixed

OK, this is in
Thanks for your work

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.