1

So I'm trying to associate new contacts and cases with an account automatically. I've been creating contacts and cases without an account using an automated script. I understand that at this point, I have to use an APEX trigger although the use case is very simple.

I'm very new to APEX triggers, so I didn't get any further than the logic.

 trigger associateWithAccount on Contact (before insert) {
//get Company__c from Contact
//lookup Accounts by Account Name using Company__c
//get AccountID from the result of that lookup
//insert AccountID from Account into AccountID in Contact
 Map<String, String> extMap = new Map<String, String>();
 Set<String> extIdSet = new Set<String>();
 for(Contact c : Trigger.new){
     extIdSet.add(c.Company__c);
 }
 for(Account a : [select Id, Name from Account where Name IN :extIdSet]){
            extMap.put(a.Id, a.Name);
 }
 for(Contact c : Trigger.new){
     c.Account = extMap.get(c.Id);
 }
}

However, I keep getting the error that I am illegally assigning from String to Account. Any help?

1

Let's rewrite this with some better variable names while recognizing that Contacts if created from the UI will most likely have their AccountId already populated

trigger associateWithAccount on Contact (before insert) {

 Map<String, Account[]> accountsByCompanyName;  // could be more than one Account w/ same name
 Set<String> searchCompanyNames = new Set<String>();

 // Step 1 - gather search keys
 for(Contact c : Trigger.new){
     if (c.AccountId == null && String.isNotBlank(c.Company__c)) {
        searchCompanyNames.add(c.Company__c);
     }
 }

 // Step 2 - find Accounts by contact.Company__c
 accountsByCompanyName = Util.pivotSObjectsByString(Account.Name,
     [Select Id, Name From Account Where Name IN :searchCompanyNames]);

 // Step 3 - Associate Contact in trigger.new to Account
 for(Contact c : Trigger.new){
    if (c.AccountId != null) {continue;}  // nothing to do 
    Account[] matchingAccounts = accountsByCompanyName.get(c.Company__c);
    if (matchingAccounts.isEmpty()) {continue;} // no match
    if (matchingAccounts.size() > 1) {continue;} // ambiguous - do nothing
    c.AccountId = matchingAccounts[0].Id; // unambiguous match
 }
} 

And handy Util methods (there are other, better flavors of this like SObjectIndex in GitHub)

public static map<String,List<SObject>> pivotSObjectsByString(Schema.SObjectField fldToken, SObject[] sobjList) {
    map<String,List<SObject>>   res = new map<String,List<SObject>> ();
    for (Sobject sobj: sobjList == null ? new list<Sobject>() : sobjlist) {
        String pivotKey = (String) sobj.get(fldToken);
        if (res.containsKey(pivotKey)) {
            res.get(pivotKey).add(sobj);
        }
        else {
            Sobject[] typedSobjs = makeTypedSobjList(sobj); // do this so resulting list.getSobjectType() doesn't return null
            typedSobjs.add(sobj);
            res.put(pivotKey,typedSobjs);
        }
    }
    return res;
}

public static Sobject[] makeTypedSobjList(Sobject sobj) {
    return (Sobject[]) Type.forName('List<'+sobj.getSObjectType()+'>').newInstance();
}
5
  • Would it be possible to extend this to create accounts in the if (matchingAccounts.isEmpty()) {continue;} clause and map it to the newly created contact? Reason I'm asking is for recursion issues, not sure if it would cause the trigger to fail
    – erikvm
    Jan 16 '20 at 14:09
  • @erikvm so, the answer is YES but you would need to break step 3 into two passes, step 3a and step 3b (Step 3B is same as current step 3). Step 3a would create the new accounts and add into a new map newAccountsByCompanyName that is used to bulk insert, then merge in newAccountsByCompanyName into accountsByCompanyName map so step 3b works
    – cropredy
    Jan 17 '20 at 0:26
  • Hmm, not sure I follow. If I do step 3a before - I'm potentially inserting accounts that should not necessarily be created?
    – erikvm
    Jan 17 '20 at 11:16
  • Step 3a only creates accounts when needed. I suggest you post a new question in stackexhange with the code. Comments aren't a good way to resolve this
    – cropredy
    Jan 17 '20 at 14:56
  • I made a new post: salesforce.stackexchange.com/questions/291729/…
    – erikvm
    Jan 20 '20 at 11:40
0

Welcome to the wonderful world of Apex! A couple of points:

Typically if we want to fill in data on a record when it's being added into the system, we want to do this in the "Before" stage. Otherwise, we're performing essentially 2 operations for every record, which isn't very safe.

Your overall order of operations is right on the money, though!

Here is a trigger example from stack-exchange that is doing essentially the same thing : How to use External Ids in a Before Insert trigger? . Check out the accepted answer there :-)

7
  • The other thing is that this contact and the account I want to tie it to are created at the exact same time, so if I do a before insert I'm not sure if it will have time to show up?
    – AKor
    Sep 23 '15 at 21:22
  • You definitely want to create the account first. Sep 23 '15 at 21:23
  • That's not a problem, I can set the script to a delay after creating the account and before creating the contact.
    – AKor
    Sep 23 '15 at 21:39
  • 1
    shouldnt be c.Id, should be c.Company__c in the last loop? Sep 23 '15 at 22:32
  • 1
    and the field should be c.AccountId Sep 23 '15 at 22:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.