Preface: this post is part of the Bulkify Your Code series.
View Chapter 5 quiz questions without the answers!
Chapter 5 Questions:
1. True or false. There is always a way around any Governor Limit.
2. True or false. Code will not be able to by deployed if it hits Governor Limits.
3. True or false. Without any changes to code, it’s possible for a trigger to hit Governor Limits on one run, and pass Governor Limits on another.
4. An identical search term has been added to a Map for a second time – this time with a different search result. What happens when the Map is searched using this search term?
(a) An error occurs when adding the second result
(b) The first search result is returned
(c) The second search result is returned
(d) Both search results are returned in a search
5. Different search terms are added to a Map – both with the same search result. What happens when the Map is searched?
(a) An error occurs when adding the second result
(b) When searching the first term, an error occurs
(c) When searching the second term, an error occurs
(d) The same search result is returned for both search terms
6. True or false. It’s OK to have SOQL inside a loop if you know 100% you’ll only have one record in the loop.
7. When combining SOQL queries, why is it better to query against a Set of potential values instead of a List?
Chapter 5 Practice Trigger
Write a trigger that populates an “Owner’s Manager” lookup field on opportunities based on the opp owner’s (user’s) standard ManagerId field. Don’t forget the test class!
trigger OMgr on Opportunity (before insert, before update) { // Get the set of all owners Set<Id> ownerIds = new Set<Id>(); for (Opportunity opp : Trigger.new) { ownerIds.add(opp.OwnerId); } // Create a map of users to managers List<User> users = [SELECT Id, ManagerId FROM User WHERE Id IN :ownerIds]; Map<Id, Id> userToMgr = new Map<Id, Id>(); for (User u : users) { userToMgr.put(u.Id, u.ManagerId); } // Set the opp field to the owner's manager for (Opportunity o : Trigger.new) { o.Owner_Manager__c = userToMgr.get(o.OwnerId); } }
Test class:
@IsTest public class TestOMgr { static testmethod void testOppMgrs() { // Create manager User mgr = new User(); mgr.Username = 'govlimitking@gmail.com'; mgr.Email = 'govlimitking@gmail.com'; mgr.FirstName = 'No'; mgr.LastName = 'Worries'; mgr.Alias = 'ez'; mgr.CommunityNickname = 'lifeisgood'; mgr.ProfileId = '00ei0000000rTfp'; mgr.TimeZoneSidKey = 'GMT'; mgr.LocaleSidKey = 'en_US'; mgr.EmailEncodingKey = 'ISO-8859-1'; mgr.LanguageLocaleKey = 'en_US'; insert mgr; // Create user w/above manager User owner = new User(); owner.Username = 'govlimitqueen@gmail.com'; owner.Email = 'govlimitqueen@gmail.com'; owner.FirstName = 'No'; owner.LastName = 'Prenup'; owner.Alias = 'dollaz'; owner.CommunityNickname = 'cashmunny'; owner.ProfileId = '00ei0000000rTfp'; owner.TimeZoneSidKey = 'GMT'; owner.LocaleSidKey = 'en_US'; owner.EmailEncodingKey = 'ISO-8859-1'; owner.LanguageLocaleKey = 'en_US'; owner.ManagerId = mgr.Id; insert owner; // Create 200 opps to test gov limits System.runAs(owner) { List<Opportunity> opps = new List<Opportunity>(); for (Integer i = 0; i < 200; i++) { Opportunity o = new Opportunity(); o.Name = 'Biggest Deal of All Time'; o.Amount = 10; o.CloseDate = Date.today(); o.StageName = 'Prospecting'; opps.add(o); } insert opps; } // Make sure everything worked! List<Opportunity> newOpps = [SELECT Id, Owner_Manager__c FROM Opportunity]; for (Opportunity o : newOpps) { System.assertEquals(mgr.Id, o.Owner_Manager__c); } } }
Please let me know where I went wrong, why cant i use map? Please provide more explanation of what the data types should be when using maps.
trigger OppManager on Opportunity (before insert, before update) {
Set allOppOwnerIds = new Set();
for(Opportunity newOpp : Trigger.new){
if(newOpp.owner != Null){
allOppOwnerIds.add(newOpp.ownerId); }
}
List userManagersList = [Select Id, ManagerId From User
Where Id = :allOppOwnerIds];
Map oppOwnerToUserId = new Map();
for(User u : userManagersList){
oppOwnerToUserId.put(u.ManagerId, u);
}
for(Opportunity newOpp : Trigger.new){
if(newOpp.owner != Null){
User oppOwner = oppOwnerToUserId.get(newOpp.ownerId);
if(oppOwner != Null){
newOpp.Owners_Manager__c = oppOwner.Id;
}
}
}
}
Hi David,
I know this post is old not sure if you would reply. Just wanted to get my solution cross checked so that I know I was on the right track.
trigger PopulateManager on Opportunity (before insert,before Update) {
Map oppMap=new Map();
List oppList=[Select Id,Owner.ManagerId from Opportunity where Id IN :Trigger.New];
for(Opportunity opp: oppList){
oppMap.put(opp.Id, opp);
}
for(Opportunity opp: Trigger.New){
Id Manager=oppMap.get(opp.Id).Owner.ManagerId;
System.debug(‘Manager: ‘+ Manager);
opp.Owner_Manager__c=Manager;
}
}
Thanks
I have done a major blunder of using before insert in my case since I am querying Id of Opp.
Hi David,
I’m wondering why my code here didn’t work as expected. It’s not recognizing that the Owner Object is not null… I didn’t think that the SOQL and Maps were necessary as all of the information should be in the Opportunity’s Owner’s information.
trigger popOwnerManager on Opportunity (before insert ) {
for (Opportunity opp : Trigger.new) {
System.debug(opp.Name + ‘ is in the trigger’); //Returns the name properly
System.debug(opp.OwnerId); //Returns my id
System.debug(opp.Owner.ManagerId); //Returns null
System.debug(opp.Owner); //Also returns null
if (opp.Owner.ManagerId != null) {
System.debug(opp.Owner.ManagerId + ‘ will be the new Owner\’s Manager.’);
opp.OwnersManager__c = opp.Owner.ManagerId;
} else{
System.debug(‘opp.Owner.Manager.Id = null’);
}
}
}
Why isn’t it recognizing the Owner Object? If it did, then no SOQL would be necessary.
Thank you so much for putting this website together!
Best Regards,
Parker Edelmann
Gotta use SOQL to get information from other objects!
Basically, if you have more than one period, you need SOQL.
Examples with one period:
opp.Name
opp.OwnerId
Examples with more than one period:
opp.Owner.ManagerId
opp.Owner.Manager.CreatedById
Cheers!
David
when i am running this test class i am getting this error “INVALID_CROSS_REFERENCE_KEY, invalid cross reference id: []” can u please explain thanks in advance
same problem here. when i copy paste the solution i also get an error:
System.DmlException: Insert failed. First exception on row 0; first error: INVALID_CROSS_REFERENCE_KEY, invalid cross reference id: []
Class.Test_OwnerManager.testOppMgrs: line 17, column 1
“ownerIds.add(opp.OwnerId)”
“userToMgr.put(u.Id, u.ManagerId);”
the field names i have seen them as owner and manager in opportunity and user objects
do we need to append id to those fields?
thanks in advance
Radha,
Owner and Manager are lookup fields that’s why it’s a must to append Id to those field names.
thank u for answering my basic questions
Instead of using map I used lists, this trigger is not working, the potential duplicate fields are not getting updated
can u please check out this
thank u
trigger leadandcontactdupes on Lead (before insert ,before update)
{
set names = new set();
for(lead trlead : trigger.new)
names.add(trlead.name);
List leads = new List([select id , name from lead where name in : names]);
List contacts = new List([select id,name from contact where name in : names]);
//for each record being inserted
for(lead trlead : trigger.new)
{
//compare with names of all leads
for(lead ld:leads)
{
if (trlead.name == ld.name)
trlead.Lead_potential_duplicate__c=ld.id;
}
// compare with all names in contacts
for(contact ct:contacts)
{
if(trlead.name == ct.name)
trlead.contact_potential_duplicate__c=ct.id;
}
}
}
Save yourself the trouble and use a Map instead! Start good habits early!
List contacts = new List([select id,name from contact where name in : trigger.new]);
i am getting error” invalid bind expression—-”
please explain thank u
Trigger.new is a list of records, and you’re trying to compare a “Name” field with an entire record!
You need to create a separate list of names from Trigger.new and use that instead.
thank u
You can further optimize the SOQL and Map code to:
Map userToMgr = new Map();
for (User u : [SELECT Id, ManagerId
FROM User
WHERE Id IN :ownerIds]) {
userToMgr.put(u.Id, u.ManagerId);
}
A little help towards avoiding heap size limit ;)
Hey David,
I know we are are trying to implement the “Owner’s manager” logic using a lookup field (which needs code) and this again might be for practice but if we really wanted to implement this, wouldn’t a custom formula field (text type) have been more appropriate and recommended in this scenario?
This one’s definitely for practice!
That said, the scenario could be that you need many fields from the owner’s manager in a report, and it wouldn’t make sense to make a ton of formula fields as a workaround.
Hey David,
here’s my stab at it
trigger AssignManager on Opportunity (before insert) {
Set allOwners= new Set();
for (Opportunity opp: Trigger.new){
allOwners.add(opp.OwnerID);
}
if(allOwners!= null && allOwners.size()>0){
Map managerToUserMap = new Map();
for(User u : [Select Id,ManagerId from User where Id in :allOwners]){
managerToUserMap.put(u.ID,u.ManagerId);
}
for (Opportunity opp: Trigger.new){
opp.OwnerManager__c=managerToUserMap.get(opp.OwnerID);
}
}
}
Hi David,
I’m just comparing your test class to mine on the practice trigger above.
I had also included a section where I updated the opportunity to be owner by the manager figuring I should also test:
1) the before update side
2) a user without a manager as the owner
Are both of those things something that should be included or are they unnecessary in this case? If so, why? If I comment out that section I still get 100% coverage but I’m not testing the before update and I would have thought the test coverage would have picked that up.
Thanks again!
Those are both great test cases!!
If this were the DEV 501 test you woulda just got some extra points!
Hi David!
Quick question. I noticed that on number 6 you said that it’s never okay to have a SOQL query in a loop. However, isn’t that what you did for the solution to Chapter 4’s Practice Trigger?
Great observation =) At the time of Chapter 4, I had not taught bulkification yet so I did not require that in the quiz =)
Hi David,
For the first part of the trigger, why would the following not work?:
for (Opportunity opp :Trigger.new){
List owner = [SELECT Id, ManagerId from User where Id =:opp.OwnerId];
Thank you so much, and thanks for such an amazing site!!
Technically it would work but you’d run into governor limits on any bulk data jobs, breaking your code!
(check out the first post in chapter 5 for more info!)
Bad idea to have SOQL inside for loop Alison
Hello David,
I am bit late to do this quizz :-)). I got this error in Saleforce. What I understand is it’s trying to tell me that I am trying to math an object with Id but Owner_Manager__c is a custom field that I have created in Opportunity.
Would you please help me to settle this problem ? Thank you in advance
I have copied the full code as following:
Error Error: Compile Error: Illegal assignment from Object to Id at line 21 column 2
****** This is the last line of code u.Owner_Manager__c = u.get(u.OwnerId); *********
trigger ManagerName on Opportunity (before insert, before update){
// Step1: Create a set of Id to query
Set ownerId = new Set();
for(Opportunity newOpp : trigger.new){
OwnerId.add(newOpp.OwnerId);
}
// Step 2: Query for Manager Id of Owner
List mId = [SELECT Id, ManagerId from USER WHERE Id = :ownerId];
// Step 3: Create a map of Owner with Manager.
Map OwnerToManager = new Map();
for(User T : mId){
T.put(T.id,T.ManagerId);
}
// Step 4: Search for Manager
for (Opportunity u : trigger.new){
u.Owner_Manager__c = u.get(u.OwnerId);
}
}
// Step 4: Search for Manager
for (Opportunity u : trigger.new){
u.Owner_Manager__c = OwnerToManager.get(u.OwnerId);
}
Hi David
I stumbled upon your site when looking for good resources to learn Apex and I must say you’ve done an awesome job at maintaining such a wonderful site.I am new to Apex and after reading the first five chapters, I feel really confident.Thank you for putting in so much effort to help others learn Apex.I tried to write the practice trigger and realized my version differs from the answer you have given.Could you please let me know if the following version is also ‘acceptable’ if not the ‘best’.
Trigger getOppOwnerManager on Opportunity(before insert)
{
// STEP 1 – Get the set of opportunity owners
SET oppowners= new SET();
for(opportunity opp : trigger.new)
{
if(opp.owner != NULL)
{
oppowners.add(opp.owner);
}
}
// STEP 2 – Get the list of users whose name is present as an opportunity owner name
List managers = new list();
managers = [select id,manager__c from user where name in : oppowners];
// I have a custom field called manager on the user object that has a hierarachical relationship.
// STEP 3 – Create a map where the keys are opportunity owner names and the values are users objects(with manager field)
Map ownersmanager = new MAP();
for(user u : managers)
{
ownersmanager.put(u.name,u);
}
for(opportunity opp : trigger.new)
{
if(opp.owner != null)
{
user oppmanager = ownermanager.get(opp.owner)
opp.ownersmanager__c = oppmanager.manager__c;
}
}
}
Thanks in advance.
You should be very proud of your code =)
I would tell you that if it works, then what you have here is good code. That’s not always the case, but I can tell this based off the general structure of your code. I can’t tell if it works because the commenting system on my site takes out some fundamental characters.
The fundamental difference between what you have written here and what I wrote in the answer is that you use the Owner Name as a key where I use the Owner ID as a key. Both work. The ID is slightly better in a highly, highly, highly unlikely scenario where there are multiple different owners with the exact same name. Even then, your code will still work and not break. So, short answer you should be very proud of your code.
In the future consider using IDs as they are guaranteed 100% unique. If you ever work in a large org (millions of records) with a duplicate problem then IDs will be your only real option as you’ll hit governor limits otherwise.
Keep on coding, you’re making great progress. I look at my user statistics and by far the majority don’t make it to Chapter 5, so you have an unusually strong will. This will get you far as a coder. If you ever make it one day as a Salesforce developer come find me I would like to talk to you.
David
David,
Thank you :-).Thank you very much for the inspiring words..Coming from you, they mean a lot.
I will keep your suggestions in mind when writing new code snippets.Eagerly waiting for your explanation on batch and scheduler apex as well as integration concepts.Also, I would like to start learning Visualforce since every SFDC developer is expected to know Visualforce in addition to Apex.Could you suggest the best way to go about it?
Thanks
Interesting fact: no Salesforce developer interview I’ve ever had touched Visualforce (even though I spend a lot of time doing it!).
I also usually don’t test people on Visualforce. This is for a few reasons:
– the Apex side of things is more difficult. The thought process generally requires deeper thinking
– Apex is much less forgiving. There may be 20 ways to do something in Visualforce but only a few ways in Apex
You should still study Visualforce though I highly recommend it. Learning from scratch from the official guide is not as difficult as learning Apex from the guide:
http://www.salesforce.com/us/developer/docs/pages/index_Left.htm
Other than that, stay tuned because I’m already thinking about how to best teach Visualforce to you guys (after advanced Apex)!
Shouldn’t the answer to Q4 actually be (c)? If second search result replaces the first one because it uses the same search term as the key, when the set is queried the second (last one added) search result would be returned.
Great call Laci!!!!!!!!!!!! Thank you and updated!!!
I am also glad that people are actually reading these hahahaha.
David