I ran into an interesting issue while using a standard list controller on a visualforce page today. This page has been working fine for a while but recently encountered an error due to a new trigger. The trigger was “bulkified” so I found it odd that the following error was being thrown:
System.LimitException: Too many SOQL queries: 101
Let me set this up for you. The visualforce page is a simple page (edited here to be even simpler) that is invoked from a list of opportunities. The user can select any opportunities in the list view, click Edit Selected Opportunities and quickly update basic info on the opportunities (there are more fields on the actual page, but I will just include Name and Stage for brevity).
List View:
Image may be NSFW.
Clik here to view.
VisualforcePage:
Image may be NSFW.
Clik here to view.
Visualforce Code:
<apex:page standardController="Opportunity" recordSetVar="opps" sidebar="false"> <apex:form id="theForm"> <apex:pageBlock > <apex:pageMessages /> <apex:pageBlockButtons > <apex:commandButton action="{!save}" value="Save All and Close"/> <apex:commandButton action="{!cancel}" value="Cancel"/> </apex:pageBlockButtons> <apex:pageBlockTable width="100%" value="{!selected}" var="o" id="theUSList" rendered="{!$User.US_User__c}"> <apex:column value="{!o.AccountId}"/> <apex:column headerValue="Opportunity Name"> <apex:inputField value="{!o.Name}"/> </apex:column> <apex:column HeaderValue="Stage"> <apex:inputField value="{!o.StageName}"/> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
Problem
One of my users was using the tool after a new trigger was implemented and ran into the above mentioned error. It turned out that he was working with 53 records, which should work fine.
After some investigation it turned out that the standard save functionality was saving each record individually. These individual saves caused the trigger to fire for each record, rather than working on the set in bulk. Not cool salesforce.com, not cool at all. The problem was, there are 2 SOQL statements in the trigger so anything more than 50 records exceeded the limit of 100 SOQL queries. So to counteract this issue I wrote a controller extension which simply grabs the selected opportunities and saves them when the button is clicked.
Note, I had to change two lines in the above VF code
1. The page tag was changed to :
<apex:page standardController="Opportunity" <strong>extensions="selectedOpportunityEditorExtension"</strong> recordSetVar="opps" sidebar="false">
2. The save button was chaged to:
<apex:commandButton action="{!save<strong>Records</strong>}" value="Save All and Close"/>
Apex Class
public with sharing class selectedOpportunityEditorExtension { //Our list of opportunities to work on private final List<Opportunity> opps; //set the list to the selected opportunites. Same list that the page is working on public selectedOpportunityEditorExtension(ApexPages.StandardSetController controller) { this.opps = controller.getSelected(); } //save the opportunities and return a page refernce to the retURL public PageReference SaveRecords(){ update opps; PageReference pageRef = new PageReference(ApexPages.currentPage().getParameters().get('retURL')); return pageRef; } }
Test Method
@isTest private class testSelectedOpportunityEditorExtension { static testMethod void myUnitTest() { //set up the page reference to the selectedOpportunityEditor and add retURL param PageReference pageRef = new PageReference('SelectedOpportunityEditor'); //add the return URL to the list view pageRef.getParameters().put('retURL','/006'); Test.setCurrentPageReference(pageRef); List<Opportunity> oppsList = new List<Opportunity>(); //Use the current user as owner of new opps Id owner = UserInfo.getUserId(); //Create 101 new opportunties to test with for(Integer i=1; i<102; i++){ oppsList.add(new Opportunity(Name='Opportunity ' + i, OwnerId = owner, Net_Gain__c = 100, StageName = 'Some Stage', Probability = 0.2, CloseDate=Date.newInstance(2100,01,01), Description='A description', Sales_Manager__c=owner )); } insert oppsList; //set up the set controller then select all opps ApexPages.StandardSetController ssc = new ApexPages.StandardSetController(oppsList); ssc.setSelected(oppsList); //initialize the extension selectedOpportunityEditorExtension sOEE = new selectedOpportunityEditorExtension(ssc); //Start the test Test.startTest(); //get selected opportunites and change the Net gain for each List<Opportunity> loopList = ssc.getSelected(); for(Opportunity o : loopList){ o.Net_Gain__c = 200; } //call the save records method and make sure it returns the right pagereference system.assertEquals('/006', sOEE.SaveRecords().getUrl() ); //Check that all opportunities were in fact updated loopList = ssc.getSelected(); for(Opportunity o : loopList){ System.AssertEquals(200, o.Net_Gain__c); } Test.stopTest(); } }
I hope that this proves useful for somebody. Please note the above code may be altered for this post and is specific to my companies org so it won’t work for you. It is just meant to serve as an example.
A special thanks to Alex Berg for helping me work though this issue.
Image may be NSFW.
Clik here to view. Image may be NSFW.
Clik here to view.