When a customer enters a new Contact with Donation information into Gift Entry and clicks the Save and New button, we do a Dry Run on the data while creating the Data Import record. If the Contact information does not match any existing Contacts, we still add the Contact Imported value (which is null in this case) to a Set of Ids called setConId. Later in the transaction, we check for matching donations in the BDI_MatchDonations class. If the size of setConId (and an Account and Opportunity Set) are all equal to 0, we bail out of the method. Otherwise, we query for all open Opportunities and their Payments where the Primary Contact is in the set of Contact Ids (in this case, null). This can lead to governor limits being hit in customer orgs where they have over 50,000 Opportunities with no Primary Contact (many times, Organization Account gifts).
I added some System.debug() lines to BDI_MatchDonations prior to the previous code block to prove my theory that setConId’s size was not 0. It resolve to 1 and ran a query which pulled in 10,000 Opportunities in my org.
// bail out if no donations to import!
System.debug('setConId size: '+ setConId.size());
System.debug('setCondId contents: ' + setConId);
if (setOppId.size() == 0 && setAccId.size() == 0 && setConId.size() == 0) {
return null;
}
Debug Log Lines:
10:31:08.156 (1196572709)|USER_DEBUG|[454]|DEBUG|setConId size: 1
10:31:08.156 (1196644688)|USER_DEBUG|[455]|DEBUG|setCondId contents: {null}
10:31:08.156 (1200858571)|SOQL_EXECUTE_BEGIN|[520]|Aggregations:1|SELECT Id,Name,Amount,AccountId,Primary_Contact__c,CloseDate,npe01__Number_of_Payments__c,Account.npe01__SYSTEMIsIndividual__c,Description,Type,npe01__Membership_End_Date__c,npe01__Membership_Start_Date__c,CampaignId,npe01__Member_Level__c,CommitmentId__c,RecordTypeId,StageName,npe01__Membership_Origin__c, (SELECT Id,Name,npe01__Paid__c,npe01__Opportunity__c,npe01__Payment_Amount__c,npe01__Payment_Date__c,npe01__Scheduled_Date__c,npe01__Written_Off__c,Type__c,ACH_Last_4__c,Gateway_ID__c,Elevate_Payment_API_Declined_Reason__c,Elevate_Payment_ID__c,Card_Network__c,Elevate_Original_Payment_ID__c,Authorized_Date__c,Authorized_UTC_Timestamp__c,Elevate_Payment_Created_UTC_Timestamp__c,ACH_Code__c,Card_Last_4__c,Donor_Cover_Amount__c,npe01__Payment_Method__c,Elevate_Payment_API_Status__c,Origin_Name__c,Origin_ID__c,Elevate_Payment_Created_Date__c,npe01__Check_Reference_Number__c,Gateway_Payment_ID__c,Card_Expiration_Month__c,Card_Expiration_Year__c,Origin_Type__c FROM npe01__OppPayment__r WHERE npe01__Paid__c = false AND npe01__Written_Off__c = false) FROM Opportunity WHERE IsClosed = false AND ( Primary_Contact__c IN :setConId )
10:31:10.798 (3798209112)|SOQL_EXECUTE_END|[520]|Rows:10000
Workarounds:
Until we resolve this in the product, this will occur for any new Contacts entered in Gift Entry that don’t match existing Contacts. One workaround would be to create the Contacts first and then use Gift Entry to create the donations.
Steps to Repeat
Enable Advanced Mapping and Gift Entry.
Create a Contact (First Name = Jess; Last Name = Lopez).
Create an Account with 50,000 Pledged Opportunities. Make sure the Primary Contact field is blank on these Opportunities.
Go to the Gift Entry Tab and create a Template.
Create a Batch.
Enter a new Gift in the Batch. Enter Jess Lopez as the existing Contact along with donation information. Click Save and New. Confirm the record is successfully added.
Now enter a new Gift for a Contact that doesn’t exist (First Name = ABC, LastName = DEF). Click Save and New and confirm that the following error appears: “Too many query rows: 50001”.
This can also be done with a smaller amount of Opportunities. The error won’t appear, but you can confirm that our query pulls in that number of records in the debug log (vs. not running the query at all...)
Summary:
When a customer enters a new Contact with Donation information into Gift Entry and clicks the Save and New button, we do a Dry Run on the data while creating the Data Import record. If the Contact information does not match any existing Contacts, we still add the Contact Imported value (which is null in this case) to a Set of Ids called setConId. Later in the transaction, we check for matching donations in the BDI_MatchDonations class. If the size of setConId (and an Account and Opportunity Set) are all equal to 0, we bail out of the method. Otherwise, we query for all open Opportunities and their Payments where the Primary Contact is in the set of Contact Ids (in this case, null). This can lead to governor limits being hit in customer orgs where they have over 50,000 Opportunities with no Primary Contact (many times, Organization Account gifts).
Root Cause
When we call getPotentialDonationMatches() in BDI_MatchDonations, we add a null value to the setConId on lines 444-446: https://github.com/SalesforceFoundation/NPSP/blob/1139b796ed082e2d1872385fa4c8fb831e8adb84/force-app/main/default/classes/BDI_MatchDonations.cls#L444-L446 In the next code block, we check to see if that set size is 0 (along with setOppId and setAccId) and if so, we return out of the function. https://github.com/SalesforceFoundation/NPSP/blob/1139b796ed082e2d1872385fa4c8fb831e8adb84/force-app/main/default/classes/BDI_MatchDonations.cls#L453-L456 In this case, the null value results in the setConId size being 1, so we don’t return out of the function and instead continue on and run a SOQL query on all open Opportunities with Payments where the Primary Contact Id is in setConId (in this case, null).
I added some System.debug() lines to BDI_MatchDonations prior to the previous code block to prove my theory that setConId’s size was not 0. It resolve to 1 and ran a query which pulled in 10,000 Opportunities in my org.
Debug Log Lines:
Workarounds:
Until we resolve this in the product, this will occur for any new Contacts entered in Gift Entry that don’t match existing Contacts. One workaround would be to create the Contacts first and then use Gift Entry to create the donations.
Steps to Repeat