0

I have a trigger that works just fine for single records and I am stumped how to make it work for multiple records. Any help would be appreciated.

trigger TSSEducationUpdate on Education__c (after insert, after update, before delete) {
List<String> TSSEduStatus = new List <String> {};

Set<Id> setContactEdu = new Set<Id>();

if(!Trigger.isDelete){


    for(Education__c thisedu: trigger.new){
        if(thisedu.Student__c !=null)
        {
            setContactEdu.add(thisedu.Student__c);
        }
        //Get Contact Associated with the Education Record
        //????? Does this need to be a "List" or should it just be a single record?
        List<Contact> c=[SELECT Id,Name FROM Contact WHERE Id IN:setContactEdu limit 1000];
        List<Education__c> edu=[Select Id,RecordTypeID, School__r.Name,School__r.RecordTypeID,School_Status__c From Education__c Where Student__c=:c];
        for(Contact con:c){
        //Create string to store Education Status List
        String TSSlist ='';

        for(Education__c e : edu){
            //Check to see if the Education Record is Record Type "The Seattle School"
            if(e.RecordTypeID==TSSRecordTypes.GetRecordTypeId('Education__c','The Seattle School')){
            //Populate list with Status Picklist values
            TSSList += e.School_Status__c +';';

            }
        }
         con.TSS_Education_Status__c=TSSList;
    }
    if(c.size() > 0)
try{
   update c;
}catch(DMLException e){
       system.debug('ERROR UPDATING TSS EDUCATION STATUS:'+e.getDMLMessage(0));
  }
}

} else  {
    //The same a above, but for update, not delete
    for(Education__c thisedu : trigger.old){
        {
        setContactEdu.add(thisedu.Student__c);
        }
        List<Contact> c=[SELECT Id,Name FROM Contact WHERE Id IN:setContactEdu limit 1000]; 
        List<Education__c> edu=[Select id,RecordTypeID, School__r.Name,School__r.RecordTypeID,School_Status__c From Education__c Where Student__c=:c  and id <> :thisedu.id];


    for(Contact con:c){
        String TSSlist ='';

        for(Education__c e : edu){
            if(e.RecordTypeID==TSSRecordTypes.GetRecordTypeId('Education__c','The Seattle School')){
            TSSList += e.School_Status__c +';';

            }
        }
         con.TSS_Education_Status__c=TSSList;
    }
    if(c.size() > 0)
try{
   update c;
}catch(DMLException e){
       system.debug('ERROR UPDATING TSS EDUCATION STATUS:'+e.getDMLMessage(0));
  }
}
}   
    //end trigger
    }
  • 2
    Google trigger bulkification and there is a plethora of info out there. – Phil Hawthorn Jun 18 '13 at 16:19
  • What happens when your trigger fires for multiple objects? What problems are you experiencing? – Mike Chale Jun 18 '13 at 16:28
  • Mike,Thanks for asking. Here is the error:2013-06-17T15:37:50-07:00,,"",,Insert,,,,," Message: TSSEducationUpdate: System.LimitException: Too many SOQL queries: 101 Status code: 13" – user2479 Jun 18 '13 at 20:32
4
trigger TSSEducationUpdate on Education__c (after insert, after update, after delete) {
    // Variables for this trigger

    // Stores all status values for the student
    Map<Id, String[]> statusList = new Map<Id, String[]>();

    // Stores all students in the trigger
    Map<Id, Contact> students = new Map<Id, Contact>();

    // Store contacts that didn't update
    Map<Id, String> failedUpdates = new Map<Id, String>();

    // The final list of contacts
    Contact[] studentList;

    // Populate initial data.
    for(Education__c record:Trigger.isDelete?Trigger.old:Trigger.new) {
        // Don't query when you don't need to. Just make in-memory copies.
        students.put(record.Student__c,new Contact(Id=record.Student__c);
        statusList.put(record.Student__c,new String[0]);
    }

    // Ignore Education without Student
    students.remove(null);

    // Find all education records matching the given record type for all students
    // Note that we filter Education__c records that we plan on ignoring to reduce
    // number of rows returned. You should probably use "DeveloperName" instead,
    // though. We also don't query fields we don't use.
    for(Education__c record:
        [SELECT Id, Student__c, School_Status__c 
         FROM   Education__c
         WHERE  Student__c IN :students.keySet() 
            AND RecordType.Name='The Seattle School']) {
        statusList.get(record.Student__c).add(record.School_Status__c);
    }

    // Populate the student status string.
    for(Contact record:students.values()) {
        // String.join can quickly build delimited lists for you. Use it.
        record.TSS_Education_Status__c = String.join(statusList.get(record.id),';');
    }

    // We need to know the order of the records.
    studentList = students.values();

    // Apex Code no longer calls a DML on empty list, so no need to "if *.size() > 0"
    Database.SaveResult[] results = Database.update(studentList, false);

    // Build error messages for students that didn't update
    for(Integer counter = 0; counter < results.size(); counter++) {
        if(!results[counter].isSuccess()) {
            String[] errors = new String[0];
            for(Database.error failReason:results[counter].getErrors()) {
                errors.add(
                    (failReason.getFields().isEmpty()?
                        '':
                        '('+String.join(failReason.getFields(),',')+') ')+
                    failReason.getMessage());
            }
            failedUpdates.put(studentList[counter].Id,'Failed to update student: '+String.join(errors,'; '));
        }
    }

    // Fail Education records conditionally if students failed to update
    for(Education__c record:Trigger.isDelete?Trigger.old:Trigger.new) {
        if(failedUpdates.containsKey(record.Student__c)) {
            record.addError(failedUpdates.get(record.Student__c));
        }
    }
}

Several notes here:

  • "?:" notation makes the code more compact. I use this notation to reduce the number of lines I need to cover with code coverage. It simply means "if condition ? value when true : value when false", and can be used to great effect when used with care.

  • As the comments state, don't query when you don't need to. Also, use filters to reduce the query load (less rows, less script statements, runs faster).

  • For bulkification, use maps and sets, as demonstrated in this version. There is always a way to gather data together in a way that allows you to use just one query instead of one per row. It's up to the developer to seek out that way.

  • I changed the DML events to after delete. This way, education records won't include their status value for deleted education events.

  • You could change the statusList to a Set<Id> if you wanted to just know which status values they are using, without duplication.

  • Reuse values when possible. I have a feeling that TSSRecordTypes.GetRecordTypeId probably doesn't use a caching mechanism, which may be one of the culprits of your query limit problems (but, I could be wrong). The best practice, though, is to simply get the value from your utility function once, place it in a variable, and reuse it as needed.

  • When you have to copy and paste code to re-use it, you probably have a design flaw. Look for ways to optimize the code so that it isn't written more than once.

  • My re-write shows how to not use try-catch for this simple case. You can use try-catch, just make sure that you're processing all of the errors correctly, and not just debugging them away. It makes it a lot harder to figure out why your code is broken later.

| improve this answer | |

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.