0

I have requirement to populate contact numbering when contact is getting created. If a contact is 3rd contact of respective account the Position of contact would be 3 and if new contact come on same account the position number would be 4 and so on.

Now code is working fine. But when two users are inserting contact at same time for one account the bot are getting same number(position). Any idea how can I resolve this?

Code snippet

Set<string> accountIDset=new Set<string>();
  Map<string,Integer> accountvsContact=new Map<string,Integer>();
  for(Contact con:Trigger.new){
    if(con.accountID!=null){
        accountIDset.add(con.accountID);
    }
  }
  for(Contact con:[select id from contact where accountID IN: accountIDset]){
      if(accountvsContact.containskey(con.accountID)){
          accountvsContact.put(con.accountID,accountvsContact.get(con.accountID)+1);
      }else{
          accountvsContact.put(con.accountID,1);
      }
  }
  for(contact con:Trigger.new){
    if(accountvsContact?.size()>0&&accountvsContact.containskey(con.accountID)){
        con.position__c=accountvsContact.get(con.accountID)+1;
      }else{
        con.position__c=1;
      }
  }

I guess this is because of different transaction for different users.

3
  • Smells like a XY Problem. Why do you need to number them, what are you trying to achieve? As a quick and dirty solution, a related list (account -> contact) can be ordered by created date.
    – identigral
    Commented Dec 19, 2023 at 17:26
  • related: Mutiex locks using custom objects: salesforce.stackexchange.com/questions/264267/…
    – cropredy
    Commented Dec 19, 2023 at 19:33
  • These are strange requirements. What is the purpose of ordering the contacts this way? What happens if you delete a contact? Or if you accidentally create a contact in the wrong account and then later move it to a new account? Commented Dec 27, 2023 at 16:57

2 Answers 2

0

This issue occurs due to multiple users creating contacts for the same account simultaneously. Since each user operates in a separate transaction, they might retrieve the same count for contacts associated with an account before committing their own new contact creation, resulting in duplicated position numbers.

To handle this situation and avoid duplicate position numbers being assigned, you can use the FOR UPDATE clause in your SOQL query to lock the records while counting them. This will prevent other transactions from accessing the same records until the current transaction is completed, ensuring the counts are accurate and sequential.

Example:

Set<String> accountIDset = new Set<String>();
Map<String, Integer> accountvsContact = new Map<String, Integer>();

for (Contact con : Trigger.new) {
    if (con.AccountId != null) {
        accountIDset.add(con.AccountId);
    }
}

// Lock the records to prevent concurrent access
List<Contact> lockedContacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIDset FOR UPDATE];

for (Contact con : lockedContacts) {
    if (accountvsContact.containsKey(con.AccountId)) {
        accountvsContact.put(con.AccountId, accountvsContact.get(con.AccountId) + 1);
    } else {
        accountvsContact.put(con.AccountId, 1);
    }
}

for (Contact con : Trigger.new) {
    if (accountvsContact.containsKey(con.AccountId)) {
        con.Position__c = accountvsContact.get(con.AccountId) + 1;
        accountvsContact.put(con.AccountId, con.Position__c); // Update the count
    } else {
        con.Position__c = 1;
        accountvsContact.put(con.AccountId, 1); // Initialize count for new account
    }
}

By using FOR UPDATE, you're ensuring that the records retrieved in the query are locked until the transaction completes, preventing other transactions from modifying them concurrently. This helps to maintain the integrity of the count and avoid assigning duplicate position numbers.

1
  • Hi Tushar Jadav, we are not updating/modifying the queried records. Querying records to calculate next number
    – RjT_BnsL
    Commented Dec 19, 2023 at 12:56
0

Though FOR UPDATE may look like a tempting option here. In my view this may not be a viable option here because it locks the records and other transaction may face UNABLE_TO_LOCK_ROW if lock is not released in timely manner, which may be out of your hand, as there may be other trigger logic running. Also you may have to shift your code from before insert to after insert, if your above code is written in before insert

Few alternate options here:

  1. Batch : Run a batch job to pick records created, and order by created date ASC and update the position field.

May not be good if you are looking for a real time approach.

  1. Platform Event Approach : Raise a Platform event with the List<Id> contact ids for which the position needs to be updated. Here the advantage is, if the Contact create request has been raised by multiple threads within the similar timeframe, it will combine all those requests in one and send it to Platform event listener. There you can process these request using the above logic and update Contact records.

May be good if you are looking for a real time approach as Platform events are near realtime..

If you shift to Platform event, then you can rewrite your code to look something like this:

/**
 * Created by Nagendra on 19-12-2023.
 */

trigger PositionTrigger on PositionEvent__e (after insert) {

    Map<Id,Id> contactIdAndAccountIdMap = new Map<Id,Id>();
    Map<Id, Integer> mapAccountIdByContactCount = new Map<Id, Integer>();
    Set<Id> accountIds = new Set<Id>();
    List<Contact> lstContactsTobeUpdated = new List<Contact>();
    List<Id> allContactsId = new List<Id>();

    for(PositionEvent__e eachPositionEvent : Trigger.new){
        List<Id> contactIds = (List<Id>) JSON.deserialize(eachPositionEvent.ContactIds__c, List<Id>.class);
        allContactsId.addAll(contactIds);
    }

    for(Contact eachContact : [SELECT Id, AccountId FROM Contact WHERE Id IN :allContactsId]){
        contactIdAndAccountIdMap.put(eachContact.Id, eachContact.AccountId);
    }
    accountIds.addAll(contactIdAndAccountIdMap.values());

    if (!accountIds.isEmpty()) {
        for(AggregateResult eachAccountIdByContactCount : [SELECT AccountId, COUNT(Id) ContactCount FROM Contact WHERE AccountId IN :accountIds GROUP BY AccountId]){
            mapAccountIdByContactCount.put((Id) eachAccountIdByContactCount.get('AccountId'), (Integer) eachAccountIdByContactCount.get('ContactCount'));
        }

        for(Id eachContactId : allContactsId){
            Integer contactCount = mapAccountIdByContactCount.get(contactIdAndAccountIdMap.get(eachContactId));
            Integer position = contactCount == null ? 1 : contactCount + 1;
            lstContactsTobeUpdated.add(new Contact(Id = eachContactId, Position__c = position));
        }
    }

    Database.update(lstContactsTobeUpdated, false);

}

And on Contact afterInsert Trigger:

List<PositionEvent__e> positionEvents = new List<PositionEvent__e>();
List<Id> contactIds = new List<Id>();

for(Contact eachContact : Trigger.new){
    // Decide that position needs updating based on any logic
    contactIds.add(eachContact.Id);
}

if(!contactIds.isEmpty()) {
    positionEvents.add(new PositionEvent__e(ContactIds__c = JSON.serialize(contactIds)));

    EventBus.publish(positionEvents);
}

NOTE : I have not considered logic for after deletion, if the position needs to be reupdated or not, as you dont have in your requirement.

5
  • Batch will not be suitable as user need to see the number next when he create the record.
    – RjT_BnsL
    Commented Dec 19, 2023 at 12:57
  • Did Platform Event is so realtime that user will be able to see it immediatly?
    – RjT_BnsL
    Commented Dec 19, 2023 at 12:58
  • Its async and near real time. So sometimes users may see the data immediately. But it all depends on your org resources. Commented Dec 19, 2023 at 13:25
  • is there any sync way to do it?
    – RjT_BnsL
    Commented Dec 19, 2023 at 17:50
  • Can we achieve it via flow anyhow?
    – RjT_BnsL
    Commented Dec 19, 2023 at 17:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .