1

I'll start off by saying I'm a complete noob to Salesforce and Apex Triggers.

What we've done so far is added a custom Email field to the Account Record. Account records are automatically created via external integration (magento store customers).

We also have Contact records that are created automatically from a different external integration (zendesk).

Both Records always have an email associated to them.

What we want to happen is when a new Account is created, check to see if a contact exists with the same email, if true add the new Account ID to Contact to associate them, if false, create a new Contact under that Account.

I'm thinking this needs to be done with some sort of upsert trigger whenever a new Account is created / updated?

I'm just not sure where to begin.. any help in the right direction is greatly appreciated!

2 Answers 2

2

The main thing to watch out for is that you avoid anti-patterns like querying in a loop. The way you do this is by using collections such as Set and Map. In this case, you would want your set elements and map keys to be email addresses. Because this logic will perform cross-object updates (or inserts), you should perform the logic after insert and after update.

There are also good reasons to use a handler pattern. I tend to use a slightly simpler approach as outlined below. I also keep all of the "how" questions out of the handler so I don't have to test it directly, just the trigger and the service.

Trigger

trigger Account on Account (after insert, after update)
{
    AccountTriggerHandler handle = new AccountTriggerHandler(trigger.new, trigger.oldMap);
    if (trigger.isAfter)
    {
        if (trigger.isInsert) handle.afterInsert();
        if (trigger.isUpdate) handle.afterUpdate();
    }
}

Handler

public with sharing class AccountTriggerHandler
{
    final List<Account> newRecords;
    final Map<Id, Account> oldMap;
    public AccountTriggerHandler(List<Account> newRecords, Map<Id, Account> oldMap)
    {
        this.newRecords = newRecords;
        this.oldMap = oldMap;
    }

    public void afterInsert()
    {
        AccountServices.associateContacts(newRecords);
    }

    public void afterUpdate()
    {
        AccountServices.associateContacts(AccountServices.hasEmailChanged(newRecords, oldMap));
    }
}

Service

public with sharing class AccountServices
{
    public static List<Account> hasEmailChanged
        (List<Account> newRecords, Map<Id, Account> oldMap)
    {
        List<Account> changed = new List<Account>();
        for (Account account : newRecords)
        {
            if (account.Custom_Email__c != oldMap.get(account.Id).Custom_Email__c)
                changed.add(account);
        }
        return changed;
    }

    public static void associateContacts(List<Account> accounts)
    {
        Set<String> addresses = new Set<String>();
        for (Account account : accounts) addresses.add(account.Custom_Email__c);
        // now you know all the email addresses
        // you can use this collection to get every Contact in one query

        Map<String, Contact> emailToContact = new Map<String, Contact>();
        for (Contact contact : [
            SELECT Custom_Email__c FROM Contact WHERE Custom_Email__c IN :addresses
        ])
        {
            emailToContact.put(contact.Custom_Email__c, contact);
        }
        //now you can find a Contact by its email address

        for (Account account : accounts)
        {
            String address = account.Custom_Email__c;
            Contact contact = buildContact(account, emailToContact.get(address));
            emailToContact.put(address, contact);
        }
        upsert emailToContact.values();
        // you should really wrap this in a try/catch
        // I'll leave that code up to you
    }
    static Contact buildContact(Account account, Contact existing)
    {
        Contact contact = new Contact(
            AccountId = account.Id,
            Custom_Email__c = account.Custom_Email__c
        );
        if (existing != null) contact.Id = existing.Id;
        return contact;
    }
}

As for how to handle any DmlException thrown by your update call, you can see how @sfdcfox would approach it in this thread. He is the most prolific poster in this community and the approach he outlines is canonical. You can also see an official introduction to the problem here.

10
  • Just to point out In case of account updates why we need to do these activities for accounts whose email addresses are not changed. Shouldn't we check if there is any change in email address by checking old and new accounts? Commented Dec 11, 2015 at 20:00
  • Also you are not creating contacts if you don't find any contacts with email addresses not matching with accounts email address. Commented Dec 11, 2015 at 20:03
  • Good points all.
    – Adrian Larson
    Commented Dec 11, 2015 at 20:08
  • @AdrianLarson Thanks Adrian, I'm getting the following compile error when I try your method: Error: Compile Error: Method does not exist or incorrect signature: buildContact(Account, Contact) at line 34 column 31 Any idea? Thanks!
    – AJK
    Commented Dec 14, 2015 at 19:59
  • Ah the method needs to be static.
    – Adrian Larson
    Commented Dec 14, 2015 at 20:01
2

You need to handle account insert as well as account update. For that you need to writer trigger and its handler which will have actual logic. Below is the trigger code.

trigger AccountTrigger on Account (after insert,after update) {

    if(Trigger.isInsert && Trigger.isAfter){
        AccountTriggerHandler.associateContacts(Trigger.New);
    }
    if(Trigger.isUpdate && Trigger.isAfter){
        AccountTriggerHandler.associateContacts(Trigger.New,Trigger.OldMap);
    }

}

In handler code we are checking for if there are any contacts matching with the email address and without account id since those would be already associated with some accounts. Now if you get any contact with the same email address as that of account which is getting inserted or updated then you need to associate such contact with account having same email id.

Now you need to check if there are any accounts which does not have any contact then for such accounts you need to create contacts and update its email address with the accounts email address also associate with it with account. Below code snippet does that for you.

Trigger Handler Code :

public class AccountTriggerHandler {


        public static void associateContacts(List<Account> accounts){
            Map<String,Id> emailVsAccountId= new Map<String,Id>();
            Map<Id,List<Contact>> accountIdVsContacts = new Map<Id,List<Contact>>();
            for(Account acc : accounts){
                emailVsAccountId.put(acc.Email__c,acc.Id);
            }
            List<Contact> contacts = [Select Id,AccountId,Email 
                                        From Contact 
                                      WHERE Email in :emailVsAccountId.keySet() AND AccountId != null];
            //update already existing contacts
            for(Contact con : contacts){
                con.AccountId = emailVsAccountId.get(con.Email);
                if(accountIdVsContacts.get(emailVsAccountId.get(con.Email))==null){
                    accountIdVsContacts.put(emailVsAccountId.get(con.Email),new List<Contact>());
                }
                accountIdVsContacts.get(emailVsAccountId.get(con.Email)).add(con);
            }

            //create new contacts
            for(Account acc : accounts){
                if(accountIdVsContacts.get(acc.Id)==null){//no matched contact found
                   accountIdVsContacts.put(acc.id,new List<Contact>());
                   accountIdVsContacts.get(acc.id).add(new Contact(LastName='SomeValueSinceMandatory',Email=acc.Email__c));                   
                }
            }
            List<Contact> contactsToBeUpserted = new List<Contact>();
            for(List<Contact> theContacts : accountIdVsContacts.values()){
                contactsToBeUpserted.addAll(theContacts);
            }

            upsert contactsToBeUpserted;

        }

        public static void assciateContacts(List<Account> accounts , Map<Id,Account> oldAccounts){
            List<Account> allAccountsWhoseEmailsAreUpdate = new List<Account>();
            for(Account acc : accounts){
                if(acc.Email__c != oldAccounts.get(acc.Id).Email__c){
                    allAccountsWhoseEmailsAreUpdate.add(acc);
                }
            }
            associateContacts(allAccountsWhoseEmailsAreUpdate);
        }

    }

I have written this code in notepad++ so please check for compile time errors. I Hope this helps you.

13
  • -1 for code dump with no explanation. If you append explanation for why you are doing things, I will remove my down vote.
    – Adrian Larson
    Commented Dec 11, 2015 at 19:39
  • @Adrian Larson. Thanks for pointing that out. I have updated solution with explanation. Commented Dec 11, 2015 at 19:46
  • Removed. FYI if you are doing cross-object logic best practice is to use after.
    – Adrian Larson
    Commented Dec 11, 2015 at 19:52
  • Yes, here I don't need to update accounts. I am updating my code no need to put this in before trigger. Commented Dec 11, 2015 at 19:54
  • Thanks for the help guys, so I ran your example but I'm getting the following error when I test inserting a new Account: System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, AccountTrigger: execution of AfterInsert caused by: System.StringException: Invalid id: [email protected] External entry point Trigger.AccountTrigger: line 4, column 1: []
    – AJK
    Commented Dec 11, 2015 at 22:35

You must log in to answer this question.

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