| Tweet |
The simplest way to achieve this is to insert the parent, then set the lookup relationship id on the child to the parent record id, as follows:
Account acc=new Account(Name='Blog Acc1'); insert acc; Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', AccountId=acc.id); insert cont;
the downside to this approach is that it is unlikely to scale. If I need to insert a large number of accounts and contacts, I'll have to manage the relationships myself in some way - probably using wrapper classes to combine an account and its list of contacts, with multiple iterations to insert the accounts, then populate the lookup fields. All in all quite a lot of code.
The next avenue I explored was setting the child relationship field to the parent record:
No dice on this I'm afraid - even though I've inserted the parent account first, the child contact is stored without a parent account.
One of the techniques that I came across while studying for the Technical Architect Certification was to specify the parent object via an external id. So I created an external id field on my account named Master_Id__c and inserted an account:
The next avenue I explored was setting the child relationship field to the parent record:
Account acc=new Account(Name='Blog Acc2'); insert acc; Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc); insert cont;
No dice on this I'm afraid - even though I've inserted the parent account first, the child contact is stored without a parent account.
One of the techniques that I came across while studying for the Technical Architect Certification was to specify the parent object via an external id. So I created an external id field on my account named Master_Id__c and inserted an account:
Account acc=new Account(Name='Blog Acc3', Master_Id__c='Blog Acc3'); insert acc;
Once the account is in place, I can instantiate the parent record based on the external id and set the relationship field:
Account acc=new Account(Master_Id__c='Blog Acc3'); Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc); insert cont;
Looking better, as I don't have to set ids, but I'm still inserting the parent first and then the child. Perhaps its possible to instantiate a new account and contact and then insert them later:
Account acc=new Account(Name='Blog Acc 4', Master_Id__c='Blog Acc 4'); Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=acc); insert acc; insert cont;
Once again, no dice. The same result as the second attempt - the account and contact are inserted, but the relationship is lost. Given that there's very little difference between this and the last attempt, it looks like its the name that is causing the problem. After trying a few permutations, the following code confirmed that this is the case:
Account acc=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6'); insert acc; Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Name='Blog Acc 6', Master_Id__c='Blog Acc 6')); insert cont;
This throws the following exception - System.DmlException: Insert failed. First exception on row 0; first error: INVALID_FIELD, More than 1 field provided in an external foreign key reference in entity: Account: [].
I've no idea why this exception isn't thrown when I specify a previously instantiated account rather than instantiating as part of the contact record, but there it is. Given this error message, it seemed possible to create a contact and identify the account by Name, but that didn't work either - a similar exception complaining that Name isn't an external id or indexed field.
This did guide me to the preferred solution though - simply instantiating a new account as part of the contact and only specifying the external id:
Account acc=new Account(Name='Blog Acc 7', Master_Id__c='Blog Acc 7'); insert acc; Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 7')); insert cont;
Looking a lot better, but there are still two insert statements - luckily insert statements can take a list of generic sobjects to insert, so I can insert both objects in one go. As long as the parent record is inserted first, everything works as expected:
Account acc=new Account(Name='Blog Acc 8', Master_Id__c='Blog Acc 8');
Contact cont=new Contact(FirstName='Bob', LastName='Buzzard', Account=new Account(Master_Id__c='Blog Acc 8'));
insert new List<Sobject>{acc, cont};
Great stuff, Keir. I wasn't aware that this could be done. This is most useful.
ReplyDeleteHi Bob,
ReplyDeleteI think is the first time I comment on your blog, but note I've been following it for quite long time now. Once again, superb work.
I'm actually trying to port this to a problem that I have right now in a real development. I need to keep low the number of inserts, so I followed your recipe. But I have one issue:
I created a routine to add contacts+accounts related, so after all my calculations in other non-related functions are finished, I need to insert this Account+Contact; in order to accomplish that, in the meanwhile the flow of events doesn't get to the insertion point I need to store them in a data structure capable to be inserted.
Is this possible? So far, I've tried to do it with maps, but inserts doesn't allow that.
Do you have any idea on how can I solve this?
@magandrez - Can you create a Map that is keyed by account name (or similar) and has a value of a list of generic sobjects? Then you could iterate the keyset of the map and insert the list of records associated with the key.
ReplyDeleteThanks Bob, you nailed. :)
DeleteThanks for the post Bob
ReplyDeleteBob, it's amzaing, thanks for your post.
ReplyDeleteGreat post, this should be part of native Apex docs.
ReplyDeleteHi Bob,
ReplyDeleteI have a different situation where the parent and child both have name as auto sequence fields. e.g. Order Header and order lines.
Can you suggest a way so that I can insert these with single insert call like in your example.
I wouldn't have thought the autonumbering would make any difference - as long as you specify the external id, you should be able to leave the names blank, as the system is filling those in.
DeleteVery useful technique Bob. Need little help with a different situation. I need to insert child records from a CSV (contains parent id) )and at the same time need to update parent record.
ReplyDeleteA workaround to the exeception "System.DmlException: Insert failed. First exception on row 0; first error: INVALID_FIELD, More than 1 field provided in an external foreign key reference in entity"
ReplyDeleteis to create a temp Account with just the external id field and then linking the contact to it :
Account acc = new Account(...);
Contact cont = new Contact(...);
Account tempAccount = new Account(
ExternalId__c = 'external_id'
);
cont.Account = tempAccount;
insert acc;
insert cont;
@Tom - That's exactly what I put in the blog post as the solution - not sure why you are posting the same thing here.
DeleteGreat post...
ReplyDelete