Salesforce 触发器更新错误

Salesforce Trigger Update Error

您好!

我正在 Salesforce 中处理触发器,我一直遇到一个我似乎无法解决的错误,所以我希望有更多经验的人可以帮助我回到正轨。我在 Google 上四处寻找并多次弄乱我的代码结构,但我无法找到有效的算法。

目的: 我的任务是编写一个触发器来处理维护每个开发人员案例排名所需的逻辑。每个开发人员都分配有案例,这些案例可能有也可能没有由业务确定的优先级。每个开发人员一次只能优先处理 10 个案例。任何其他情况在排名字段中只会有一个空值。如果插入、更新或删除具有排名的案例,则分配给具有排名的该开发人员的所有其他案例必须相应地自动更新。任何排名高于 10 的案例都将被剔除。

问题:我已经完成了插入和删除触发器功能。该部分代码已经过测试并且可以正常运行。我知道我的代码可能会写得更好,所以我会一直接受这方面的建议,但我想解决的主要问题是触发器中的更新逻辑。

错误:

  1. 当我尝试更新具有空值排名的案例以使其现在具有 1-10 之间的值时,我收到此错误:

There were custom validation error(s) encountered while saving the affected record(s). The first validation error encountered was "Apex trigger CaseRankingTrigger caused an unexpected exception, contact your administrator: CaseRankingTrigger: execution of BeforeUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 500M0000006sLVOIA2; first error: SELF_REFERENCE_FROM_TRIGGER, Object (id = 500M0000006sLVO) is currently in trigger CaseRankingTrigger, therefore it cannot recursively update itself: []: Trigger.CaseRankingTrigger: line 62, column 1".

  1. 当我尝试用新数字更新现有排名在 1-10 之间的案例时,出现此错误:

Error: Invalid Data. Review all error messages below to correct your data. Apex trigger CaseRankingTrigger caused an unexpected exception, contact your administrator: CaseRankingTrigger: execution of BeforeUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 500M0000006sLTrIAM; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, CaseRankingTrigger: execution of BeforeUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 500M0000006sLUaIAM; first error: SELF_REFERENCE_FROM_TRIGGER, Object (id = 500M0000006sLUa) is currently in trigger CaseRankingTrigger, therefore it cannot recursively update itself: [] Trigger.CaseRankingTrigger: line 74, column 1: []: Trigger.CaseRankingTrigger: line 62, column 1

  1. 当我尝试将具有现有排名的案例更新为空值时,该案例将变为空值,但其他具有值的案例不会向下移动以防止出现间隙。这意味着如果我有案例 1-8 并且我删除了案例 5,那么案例 6 7 和 8 应该分别向下移动到 5 6 和 7。

代码:

trigger CaseRankingTrigger on Case (before insert, before update, before delete) {
// class level variables
Integer MAX = 10;
Integer MIN = 1;
String dev;
Decimal oldRank;
Decimal newRank;
/***************************************************************************************
* This block of code fires if a new Case is being inserted into the database
***************************************************************************************/
if (trigger.isInsert) {
    // iterates through the Cases in the new trigger
    for (Case c : trigger.new) {
        // sets the developer to the developer on the Case
        dev = c.Resource_Assigned__c;
        // sets the new rank for the Case being inserted
        newRank = c.Case_Rank__c;
        // this block of code only fires if the Case rank field is not null - this allows for Cases with no rank to be inserted without affecting any other Case
        if (newRank != null) {
            // populates a list of Cases assigned to the developer if they have a ranking equal to or greater than the rank of the new Case being inserted
            List<Case> devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND Case_Rank__c >= :newRank];
            // iterates over the list of Cases and increases their rank value by 1 to create room for the new Case then inserts that list back into the database
            for (Case devCase : devCases) {
                devCase.Case_Rank__c = devCase.Case_Rank__c + 1;
            }
            update devCases;
            // populates a list of Cases for the assigned developer if they have a ranking greater than the highest rank allowed
            devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND Case_Rank__c > :MAX];
            // iterates over the list of applicable Cases with a rank greater than 10 and nulls out the rank value then inserts that list back into the database
            for (Case devCase : devCases) {
                devCase.Case_Rank__c = null;
            }
            update devCases;
        }
    }
}
/***************************************************************************************
* This block of code fires if an existing Case is being updated
***************************************************************************************/
else if (trigger.isUpdate) {
    for (Case cOld : trigger.old) {
        oldRank = cOld.Case_Rank__c;
    }
    for (Case c : trigger.new) {
        dev = c.Resource_Assigned__c;
        newRank = c.Case_Rank__c;
        if (oldRank == null && newRank != null) {
            List<Case> devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND Case_Rank__c >= :newRank];
            for (Case devCase : devCases) {
                devCase.Case_Rank__c = devCase.Case_Rank__c + 1;
            }
            update devCases;
            devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND (Case_Rank__c > :MAX OR Case_Rank__c < :MIN)];
            for (Case devCase : devCases) {
                devCase.Case_Rank__c = null;
            }
            update devCases;
        } else {
            if (newRank != null) {
                List<Case> devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND Case_Rank__c >= :newRank];
                for (Case devCase : devCases) {
                    devCase.Case_Rank__c = devCase.Case_Rank__c + 1;
                }
                update devCases;
                devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND (Case_Rank__c > :oldRank OR Case_Rank__c < :newRank)];
                for (Case devCase : devCases) {
                    devCase.Case_Rank__c = devCase.Case_Rank__c - 1;
                }
                update devCases;
                devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND (Case_Rank__c > :MAX OR Case_Rank__c < :MIN)];
                for (Case devCase : devCases) {
                    devCase.Case_Rank__c = null;
                }
                update devCases;
            }
        }
    }
}
/***************************************************************************************
* This block of code fires if an existing Case is deleted from the database
***************************************************************************************/
else if (trigger.isDelete) {
    // iterates through the Cases in the old trigger
    for (Case c : trigger.old) {
        // sets the developer to the developer on the Case
        dev = c.Resource_Assigned__c;
        // sets the old rank value for the Case being deleted
        oldRank = c.Case_Rank__c;
        // this block of code only fires if the rank field is not null - this allows for Cases with no rank to be deleted without affecting any other Case
        if (oldRank != null) {
            // populates a list of Cases assigned to the developer if they have a ranking greater than the rank of the Case being deleted
            List<Case> devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND Case_Rank__c > :oldRank];
            // iterates over the list of applicable Cases and decreases their rank by 1 so there is no gap in rank priority then inserts that list back into the database
            for (Case devCase : devCases) {
                devCase.Case_Rank__c = devCase.Case_Rank__c - 1;
            }
            update devCases;
        }
    }
}
/***************************************************************************************
* Fall Through
***************************************************************************************/
else {}

}

非常感谢大家在这方面给我的帮助!

此致,

迈克尔


最终更新:在使用@Egor 提供的建议后,我能够完成我的代码。

Trigger Code:

/***************************************************************************************
* @author:      Michael *REDACTED*
* @email:        *REDACTED*
* @date:         11/09/15
* @brief:         This is a trigger for the Case object that will modify the rank of the Cases
*                     assigned to the developer based on a priority set by the Administrator.
* @files:          src\classes\CaseRankTriggerHandler.cls
*                     src\classes\CaseRankTriggerHandlerTest.cls
*                     src\layouts\Case-Salesforce Service Ticket.layout
*                     src\objects\Case.object
*                     src\workflows\Case.workflow
***************************************************************************************/
trigger CaseRankTrigger on Case (before insert, before update, before delete) {
    /*
    * The CaseRankTriggerHandler constructor method takes (List<Case>, List<Case>, String)
    */
    if (trigger.isInsert) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(trigger.new, trigger.old, 'Insert'); // if a new Case is being inserted into the database
    if (trigger.isUpdate) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(trigger.new, trigger.old, 'Update'); // if an existing Case is being updated
    if (trigger.isDelete) CaseRankTriggerHandler handler = new CaseRankTriggerHandler(trigger.new, trigger.old, 'Delete'); // if an existing Case is deleted from the database
}

Trigger Handler Code:

/***************************************************************************************
* @author:      Michael *REDACTED*
* @email:        *REDACTED*
* @date:         11/09/15
* @brief:         This is a Case object trigger handler class that provides logic to the CaseRankTrigger for manipulating
*                     the ranks of all Cases assigned to a developer based on a priority that is set by an Administrator.
* @files:          src\classes\CaseRankTrigger.cls
*                     src\classes\CaseRankTriggerHandlerTest.cls
*                     src\layouts\Case-Salesforce Service Ticket.layout
*                     src\objects\Case.object
*                     src\workflows\Case.workflow
***************************************************************************************/
public with sharing class CaseRankTriggerHandler {
    // class level variables
    private static Boolean firstRun = true;
    private static Boolean modify = false;
    private static Integer MAX = 10;
    private static Integer MIN = 1;
    private List<Case> newTrigger {get; set;}
    private List<Case> currentTrigger {get; set;}
    private List<Case> cases {get; set;}
    private List<Case> newList {get; set;}
    private List<Case> currentList {get; set;}
    private String developer {get; set;}
    private Decimal newRank {get; set;}
    private Decimal currentRank {get; set;}
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       Class constructor method.
    * @return:     Void
    ***************************************************************************************/
    public CaseRankTriggerHandler(List<Case> newT, List<Case> oldT, String type) {
        if (firstRun) { // makes sure that the trigger only runs once
            firstRun = false;
            InitializeTrigger(newT, oldT, type); // initializes the trigger
            if (developer != null) { // skips trigger if DML is performed on a Case with no developer assigned
                ModificationCheck(type); // determines if Cases need to be modified
                if (modify) ModificationLogic(type); // modifies Cases if needed
            }
        }
    }
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       The InitializeTrigger method initializes the handler class based on the type of trigger fired.
    * @return:     Void
    ***************************************************************************************/
    private void InitializeTrigger(List<Case> newT, List<Case> oldT, String type) {
        if (type == 'Insert') {
            this.newTrigger = newT;
            this.developer = newTrigger[0].Resource_Assigned__c;
            this.newRank = newTrigger[0].Case_Rank__c;
            this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
        } else if (type == 'Update') {
            this.newTrigger = newT;
            this.currentTrigger = oldT;
            this.developer = newTrigger[0].Resource_Assigned__c;
            this.newRank = newTrigger[0].Case_Rank__c;
            this.currentRank = currentTrigger[0].Case_Rank__c;
            this.newList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :newRank ORDER BY Case_Rank__c];
            this.currentList = [SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :developer AND Case_Rank__c != null AND Case_Rank__c = :currentRank ORDER BY Case_Rank__c];
        } else if (type == 'Delete') {
            this.currentTrigger = oldT;
            this.developer = currentTrigger[0].Resource_Assigned__c;
            this.currentRank = currentTrigger[0].Case_Rank__c;
        }
    }
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       The ModificationCheck method ensures various conditions are met, depending on the type
    *                   of trigger that was fired, before modifying the ranks of the Cases assigned to the developer.
    * @return:     Void
    ***************************************************************************************/
    private void ModificationCheck(String type) {
        if (type == 'Insert') {
            // the Case being inserted has a new rank not equal to null and if the assigned developer already has a Case with the
            // same rank as the new rank, we will proceed to modification, if not the record will be inserted without modification.
            if (newRank != null && !newList.isEmpty()) {
                modify = true;
            }
        } else if (type == 'Update') {
            // if the Case being updated has ranks with different values in both triggers we will proceed to the next check, if not the record is updated without modification.
            if (newRank != currentRank) {
                // if the Case being updated has a (new rank equal to null and a current rank not equal to 10) or
                // if the Case being updated has a new rank not equal to null, we will proceed to the next check,
                // if not the record is updated without modification.
                if ((newRank == null && currentRank != 10) || newRank != null) {
                    // if the assigned developer on the Case being updated already has a Case with the same rank as the new or current rank, we will proceed to modification,
                    // if not the record is updated without modification.
                    if (!newList.isEmpty() || !currentList.isEmpty()) {
                        modify = true;
                    }
                }
            }
        } else if (type == 'Delete') {
            // if the Case being deleted has current rank not equal to null, we will proceed to modification, if not the record is deleted without modification.
            if (currentRank != null) {
                modify = true;
            }
        }
    }
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       If a Case rank needs to be updated the ModificationLogic method calls the appropriate
    *                   computation method based on trigger type and the values of newRank and currentRank.
    * @return:     Void
    ***************************************************************************************/
    private void ModificationLogic(String type) {
        if (type == 'Insert') {
            for (Case c : newTrigger) {
                // calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
                IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
            }
        } else if (type == 'Update') {
            for (Case c : newTrigger) {
                if (currentRank == null) {
                    // if the current rank is null - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than or equal to the new rank.
                    IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c >= :newRank ORDER BY Case_Rank__c]);
                } else if (newRank == null) {
                    // if the new rank is null - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
                    DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
                } else if (newRank > currentRank) {
                    // if the new rank is greater than the current rank - calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank less than or equal to the new rank and greater than to the current rank.
                    DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c <= :newRank AND Case_Rank__c > :currentRank) ORDER BY Case_Rank__c]);
                } else if (newRank < currentRank) {
                    // if the new rank is less than the current rank - calls the IncreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank a.
                    IncreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :newTrigger AND Resource_Assigned__c = :developer AND (Case_Rank__c >= :newRank AND Case_Rank__c < :currentRank) ORDER BY Case_Rank__c]);
                }
            }
        } else if (type == 'Delete') {
            for (Case c : currentTrigger) {
                // calls the DecreaseCaseRank method and passes it a list of Cases that are assigned to the developer that have a rank greater than the current rank.
                DecreaseCaseRank([SELECT Subject, CaseNumber, Case_Rank__c FROM Case WHERE Id NOT IN :currentTrigger AND Resource_Assigned__c = :developer AND Case_Rank__c > :currentRank ORDER BY Case_Rank__c]);
            }
        }
    }
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       The DecreaseCaseRank method provides the logic required to properly
    *                   decrease or null out the ranks of the Cases assigned the the developer.
    * @return:     Void
    ***************************************************************************************/
    private void DecreaseCaseRank(List<Case> cases) {
        // if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
        // list and decrease their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
        if (!cases.isEmpty()) {
            for (Case c : cases) {
                if (c.Case_Rank__c >= 1 && c.Case_Rank__c <= 10) {
                    c.Case_Rank__c = c.Case_Rank__c - 1;
                } else {
                    c.Case_Rank__c = null;
                }
            }
            update cases;
        }
        return;
    }
    /***************************************************************************************
    * @author:    Michael *REDACTED*
    * @email:      *REDACTED*
    * @date:       11/16/15
    * @brief:       The IncreaseCaseRank method provides the logic required to properly
    *                   increase or null out the ranks of the Cases assigned the the developer.
    * @return:     Void
    ***************************************************************************************/
    private void IncreaseCaseRank(List<Case> cases) {
        // if the list of Cases passed in by the ModificationLogic method isn't empty then it will iterate through the
        // list and increase their ranks by 1 or null out the rank if it is not within the acceptable limits (1-10).
        if (!cases.isEmpty()) {
            for (Case c : cases) {
                if (c.Case_Rank__c >= 1 && c.Case_Rank__c < 10) {
                    c.Case_Rank__c = c.Case_Rank__c + 1;
                } else {
                    c.Case_Rank__c = null;
                }
            }
            update cases;
        }
        return;
    }
}

问题是您正在尝试更新已触发触发器的同一个案例。

devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND (Case_Rank__c > :MAX OR Case_Rank__c < :MIN)];
        for (Case devCase : devCases) {
            devCase.Case_Rank__c = null;
        }
        update devCases

您的 select 语句也将 return 触发此触发器的情况,因为它的 Case_Rank__c 为空,因此符合条件。与 select 您 运行 相同,用于更新等级为 1-10 的案例。

尝试devCases = [SELECT Id, Case_Rank__c FROM Case WHERE Resource_Assigned__c = :dev AND (Case_Rank__c > :MAX OR Case_Rank__c < :MIN) AND Id NOT IN trigger.new];

查看 https://help.salesforce.com/apex/HTViewSolution?id=000005278&language=en_US 以了解抛出异常的描述