您的代码中有几个严重的坏事。希望你不会感到被冒犯,这是一个很好的要求,也是一个学习的机会......
after insert
在这里没有任何意义。根据定义,如果您刚刚完成插入此机会,则其中还没有任何联系人角色。*
after update
没问题。before update
会更好,因为您只需填写该值即可免费保存到数据库。
- 我认为您可能必须将此逻辑复制到 OpportunityContactRole 上的类似触发器?而不是重复代码 - 使用一些可以从两个地方调用的类?
- 我假设您只想
LeadSource
在机会为空的情况下填写它?
- 您在循环中进行了选择,这是一个非常糟糕的做法;)您的触发器不是“批量”,当有人在一个事务中更新 10 个机会(例如通过使用 Data Loader)时它可能会失败,因为我们的限制为最多 20触发器中的查询。
- 您不需要
ORDER BY
,LIMIT 1
等等 - Salesforce 将保护您并只允许 1 个联系人作为主要联系人。即使您想使用 Data Loader 以此类错误加载它们。
- 阅读有关关系如何在 Salesforce 中运作的信息,您似乎会受益匪浅。与普通 SQL 语言相比,“点表示法”和子查询看起来有点奇怪,但您可以使用它们来大大简化您的代码。如果您过去做过一些面向对象的编程,将会有所帮助。这是一些指南,这是官方文档
TL;博士
trigger fillLeadSource on Opportunity (before update) {
/* I'm assuming you want to fill LeadSource in only if it was blank.
If that's not the case - just delete this first part of code and in the query instead of ":oppsToFill" bind to ":trigger.new" or
":trigger.newMap.keyset()".
(I know they look weird but you can do it, bind objects/collections in queries that expect Ids / collections of Ids)
*/
Set<Id> oppsToFill = new Set<Id>();
for(Opportunity o : trigger.new){
if(o.LeadSource == null) {
oppsToFill.add(o.Id);
}
}
// Now we'll select all possible contacts wasting only 1 query.
if(!oppsToFill.isEmpty()){
List<OpportunityContactRole> roles = [SELECT OpportunityId, Contact.Name, Contact.LeadSource
FROM OpportunityContactRole
WHERE isPrimary = true AND Contact.LeadSource != null AND OpportunityId IN :oppsToFill];
if(!roles.isEmpty()){
for(OpportunityContactRole ocr : roles){
Opportunity oppToBeFilled = trigger.newMap.get(ocr.OpportunityId);
System.debug('Changing lead source on ' + oppToBeFilled.Name + ' from ' + oppToBeFilled.LeadSource + ' to ' + ocr.Contact.LeadSource + ' (thx to ' + ocr.Contact.Name + ' being the Primary Contact).');
oppToBeFilled.LeadSource = ocr.Contact.LeadSource;
}
}
}
// That's it. If there was a primary contact with Lead source, data will be copied over.
// If there was no primary contact or he didn't have the source filled in - tough luck, we did our best.
// Since it's before update, you get save to database for free.
}
编辑以回答评论 re#3 中的问题
before
您需要在新触发器中使用相似但不相同的代码(在这种情况下,它是或after
- 我们需要显式更新 Opportunities并不重要)。这里有点糟糕,因为您要查看的字段不是直接可用的 - 您可以访问 OpportunityId、ContactId 但不能访问 Contact.LeadSource。这样的事情应该可以解决问题:
trigger ContactRoleRollup on OpportunityContactRole(after insert, after update){
Map<Id,Id> oppToContactMap = new Map<Id, Id>();
for(OpportunityContactRole ocr : trigger.new){
if(ocr.isPrimary){
oppToContactMap.put(ocr.OpportunityId, ocr.ContactId);
}
}
if(!oppToContactMap.isEmpty()){
List<Opportunity> oppsToUpdate = [SELECT Id FROM Opportunity WHERE LeadSource = null AND Id IN :oppToContactMap.keyset()];
Map<Id, Contact> contacts = [SELECT Id, LeadSource FROM Contact WHERE LeadSource != null AND Id IN :oppToContactMap.values()];
for(Opportunity o : oppsToUpdate){
Id contactId = oppToContactMap.get(o.Id);
Contact c = contacts.get(contactId);
if(c != null){
o.LeadSource = c.LeadSource;
}
}
update oppsToUpdate;
}
}
这里很有趣,因为此更新将触发我们的旧触发器。如果您离开了我的“如果填写了leadSource,则跳过”应该没问题,但您仍然可能想探索两件事:
- 在类中使用一些辅助静态标志,将其称为“skipTriggerOnOpps”,您将在 OpportunityContactRoles 上的触发器中设置此标志,并将 Opportunity 触发器的整个代码包装在其中,这样如果我们已经处理了主要源同步,它就不会执行.
从理论上讲,您可以在不更改任何内容的情况下“触摸”机会(在这种情况下将旧触发器视为一种好处,而不是不必要的副作用)。对我来说,它看起来有点太神奇了,但是如果对这里发生的事情进行了很好的评论,它可能会导致更少的代码、更少的逻辑重复、更少的单元测试......只要它是after
触发器就可以工作,因此查询联系人角色我们刚刚修改会看到新的值。它必须看起来像那样
trigger ContactRoleRollup on OpportunityContactRole(after insert, after update){
Set<Id> oppIds = new Set<Id>();
for(OpportunityContactRole ocr : trigger.new){
if(ocr.isPrimary){
oppIds.add(ocr.OpportunityId);
}
}
if(!oppIds.isEmpty()){
update [SELECT Id FROM Opportunity WHERE LeadSource = null AND Id IN :oppIds];
// That's it. Call update directly on returned list without changing anything, let the other trigger worry about the logic
}
}