Salesforce Triggers(2)

学习目标

完成本单元后,您将能够:

  • 编写对sObject集合进行操作的触发器。
  • 编写执行高效SOQL和DML操作的触发器。

批量触发器设计模式

Apex触发器被优化以批量操作。我们建议使用批量设计模式处理触发器中的记录。当您使用批量设计模式时,您的触发器具有更好的性能,消耗更少的服务器资源,并且不太可能超出平台限制。
批量代码的好处是,批量代码可以高效地处理大量记录,并在Force.com平台的管理限制内运行。这些州长限制已经到位,以确保失控代码不会垄断多租户平台上的资源。

以下部分将演示在触发器中扩展Apex代码的主要方法:对触发器中的所有记录进行操作,并对sObject集合执行SOQL和DML,而不是同时对单个sObjects执行SOQL和DML。 SOQL和DML批量最佳实践适用于任何Apex代码,包括类中的SOQL和DML。给出的例子是基于触发器,并使用Trigger.New上下文变量。

在记录集上运行

我们先来看触发器中最基本的批量设计概念。批量触发器在触发器上下文中的所有sObjects上运行。通常情况下,如果触发触发器的操作来自用户界面,触发器将在一条记录上运行。但是,如果操作的起源是批量DML或API,则触发器将在记录集上运行,而不是一个记录。例如,当您通过API导入多条记录时,触发器将在整个记录集上进行操作。因此,一个良好的编程习惯就是始终假设触发器在一系列记录上运行,以便在任何情况下都能正常工作。

以下触发器假设只有一条记录导致触发器触发。在同一事务中插入多个记录时,此触发器不能在完整的记录集上工作。下一个示例中显示了一个批量版本。

trigger MyTriggerNotBulk on Account(before insert) {
    Account a = Trigger.New[0];
    a.Description = 'New description';
}
这个例子是MyTrigger的修改版本。它使用for循环遍历所有可用的sObjects。如果Trigger.New包含一个sObject或多个sObjects,则此循环有效。
trigger MyTriggerBulk on Account(before insert) {
    for(Account a : Trigger.New) {
        a.Description = 'New description';
    }
}
执行批量SOQL

SOQL查询可以是强大的。您可以检索相关记录,并在一个查询中检查多个条件的组合。通过使用SOQL功能,您可以编写更少的代码并减少对数据库的查询。减少数据库查询有助于避免遇到查询限制,即同步Apex的100个SOQL查询或异步Apex的200个查询限制。

以下触发器显示要避免的SOQL查询模式。这个例子在for循环中创建一个SOQL查询,以获取每个客户的相关机会,每个客户在Trigger.New中为每个Account sObject运行一次。如果您拥有大量客户,for循环中的SOQL查询可能导致太多的SOQL查询。下一个例子显示了推荐的方法。

trigger SoqlTriggerNotBulk on Account(after update) {   
    for(Account a : Trigger.New) {
        // 获取每个客户的子记录
        // 对于每个客户运行一次的SOQL查询效率低下!
        Opportunity[] opps = [SELECT Id,Name,CloseDate 
                             FROM Opportunity WHERE AccountId=:a.Id];
        
        // 做一些其他处理
    }
}
此示例是前一个示例的修改版本,并显示了运行SOQL查询的最佳实践。 SOQL查询完成繁重的工作,并在主循环之外调用一次。
  • SOQL查询使用内部查询(SELECT Id FROM Opportunities)来获取相关的客户机会。
  • SOQL查询通过使用IN子句并绑定WHERE子句中的Trigger.New变量(WHERE Id IN:Trigger.New)连接到触发器上下文记录。这个WHERE条件将客户过滤为仅触发此触发器的记录。

将查询中的两部分结合起来,就可以在一次调用中得到我们想要的记录:这个客户将触发每个客户的相关机会。

获取记录及其相关记录之后,for循环通过使用集合变量(本例中为acctsWithOpps)来迭代感兴趣的记录。 collection变量保存SOQL查询的结果。这样,for循环只能遍历我们想要操作的记录。由于相关的记录已经被获得,所以在循环内不需要进一步的查询来获得这些记录。

trigger SoqlTriggerBulk on Account(after update) {  
    // 执行一次SOQL查询。
    // 获取客户及其相关机会。
    List<Account> acctsWithOpps = 
        [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities) 
         FROM Account WHERE Id IN :Trigger.New];
  
    // 遍历返回的客户 
    for(Account a : acctsWithOpps) { 
        Opportunity[] relatedOpps = a.Opportunities;  
        // 做一些其他处理
    }
}
或者,如果您不需要客户父记录,则只能检索与此触发器上下文中的客户相关的商机。该列表在WHERE子句中通过将机会的AccountId字段与Trigger.New中的客户ID进行匹配来指定:WHERE AccountId IN:Trigger.New。返回的机会适用于此触发器上下文中的所有客户,而不是特定客户。下一个示例显示用于获取所有相关机会的查询。
trigger SoqlTriggerBulk on Account(after update) {  
    // 执行一次SOQL查询。
    // 获取这个触发器中客户的相关机会。
    List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New];
  
    // 遍历相关的机会 
    for(Opportunity opp : relatedOpps) { 
        // 做一些其他处理
    }
}
您可以通过在一个语句中将SOQL查询与for循环组合来缩小前面的示例:SOQL for循环。这是使用SOQL for循环的这个批量触发器的另一个版本。
trigger SoqlTriggerBulk on Account(after update) {  
    // 执行一次SOQL查询。  
    // 在此触发器中获取客户的相关机会,
    // 并遍历这些记录。
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New]) {
  
        // 做一些其他处理
    }
}
超越基础

触发器一次执行批次的200条记录。所以如果有400条记录触发一个触发器,那么触发器会触发两次,每个记录有一次。因为这个原因,在触发器中你没有得到SOQL的循环记录批处理的好处,因为触发器也记录了批处理记录。在这个例子中SOQL for循环被调用了两次,但是一个独立的SOQL查询也被调用了两次。但是,SOQL for循环看起来比迭代集合变量更优雅!

执行批量DML

在触发器或类中执行DML调用时,尽可能在一组sObject上执行DML调用。在每个sObject上执行DML单独使用资源效率低下。 Apex运行时允许在一个事务中多达150个DML调用。

该触发器在for循环中执行更新调用,该循环遍历相关的机会。如果满足某些条件,则触发器更新机会描述。在这个例子中,更新语句对于每个机会被低效地调用一次。如果批量客户更新操作触发了该触发器,则可能有多个客户。如果每个客户有一两个机会,我们可以很容易地结束150个机会。 DML语句限制是150个调用。

trigger DmlTriggerNotBulk on Account(after update) {   
    // 获取这个触发器中客户的相关机会。
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];          

    // 遍历相关的机会
    for(Opportunity opp : relatedOpps) {      
        // 当概率大于50%但小于100%时更新描述
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            // 为每个机会更新一次 - 效率不高!
            update opp;
        }
    }    
}
下面的例子展示了如何在一个机会列表上只用一个DML调用来高效地执行DML。该示例将添加Opportunity sObject以更新为循环中的机会列表(oppsToUpdate)。接下来,触发器在所有机会添加到列表之后,在此列表的循环外执行DML调用。无论正在更新的sObject数量如何,此模式只使用一个DML调用。
trigger DmlTriggerBulk on Account(after update) {   
    // 获取这个触发器中客户的相关机会。 
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];
          
    List<Opportunity> oppsToUpdate = new List<Opportunity>();

    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // 当概率大于50%但小于100%时更新描述
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            oppsToUpdate.add(opp);
        }
    }
    
    // 在集合上执行DML
    update oppsToUpdate;
}

批量设计模式的实际操作:获取相关记录的触发器示例

让我们通过编写一个访问客户相关机会的触发器来应用您所学习的设计模式。修改AddRelatedRecord触发器的前一单元的触发器示例。 AddRelatedRecord触发器批量操作,但效率不如它可能是因为它遍历所有Trigger.New sObject记录。下一个示例修改SOQL查询以仅获取感兴趣的记录,然后遍历这些记录。如果你还没有创建这个触发器,不要担心,你可以在本节中创建它。

让我们从AddRelatedRecord触发器的要求开始。客户被插入或更新后触发器触发。触发器为每个没有机会的客户添加一个默认机会。要解决的第一个问题是弄清楚如何获得孩子的机会记录。因为这个触发器是一个after触发器,我们可以从数据库中查询受影响的记录。他们已经被触发后触发的时间。让我们编写一个SOQL查询,返回这个触发器中没有相关机会的所有账号。

[SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND
                                             Id NOT IN (SELECT AccountId FROM Opportunity)]
现在我们已经获得了我们感兴趣的记录子集,让我们通过使用SOQL for循环遍历这些记录,如下所示。
for(Account a : [SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND
                                         Id NOT IN (SELECT AccountId FROM Opportunity)]){
}
你现在已经看到了我们触发器的基础知识。唯一缺失的部分是创建默认的机会,我们将要进行批量处理。这是完整的触发器。
  1. 如果您已经在上一个单元中创建了AddRelatedRecord触发器,请通过用以下触发器替换其内容来修改触发器。否则,请使用开发者控制台添加以下触发器,并为触发器名称输入AddRelatedRecord。
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        
        //为每个客户添加一个机会,如果它还没有。
        // 遍历此触发器中没有机会的客户。
        for (Account a : [SELECT Id,Name FROM Account
                         WHERE Id IN :Trigger.New AND
                         Id NOT IN (SELECT AccountId FROM Opportunity)]) {
            // 为此客户添加一个默认机会
            oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                       StageName='Prospecting',
                                       CloseDate=System.today().addMonths(1),
                                       AccountId=a.Id)); 
        }
        
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. 要测试触发器,请在Salesforce用户界面中创建一个客户,并将其命名为“Lions&Cats”。
  3. 在客户页面的机会相关列表中,找到新的机会狮子会&猫。触发器自动添加了机会!

Salesforce Triggers(1)

学习目标

完成本单元后,您将能够:

  • 编写一个Salesforce对象的触发器。
  • 使用触发器上下文变量
  • 从触发器调用类方法。
  • 在触发器中使用sObject addError()方法来限制保存操作。

编写 Apex触发器

Apex触发器使您能够在事件之前或之后对Salesforce中的记录(例如插入,更新或删除)执行自定义操作。就像数据库系统支持触发器一样,Apex提供触发器支持来管理记录。
通常,您可以使用触发器根据特定条件执行操作,修改相关记录或限制某些操作的发生。您可以使用触发器在Apex中执行任何操作,包括执行SOQL和DML或调用自定义的Apex方法。

使用触发器执行无法使用Salesforce用户界面中的指向点击工具完成的任务。例如,如果验证字段值或更新记录中的字段,请改为使用验证规则和工作流程规则。

可以为顶级标准对象定义触发器,例如帐户或联系人,自定义对象和一些标准子对象。触发器在创建时默认处于活动状态。当指定的数据库事件发生时,Salesforce会自动触发活动触发器。

Trigger 语法

触发器定义的语法不同于类定义的语法。触发器定义以trigger关键字开始。之后是触发器的名称,触发器所关联的Salesforce对象以及触发的条件。触发器具有以下语法:

trigger TriggerName on ObjectName (trigger_events) {
   code_block
}
要在插入,更新,删除和取消删除操作之前或之后执行触发器,请在逗号分隔列表中指定多个触发器事件。您可以指定的事件是:
  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

在插入帐户并将消息写入调试日志之前,触发这个简单的触发器。

  1. 在开发者控制台中,点击 File | New | Apex Trigger.
  2. 输入HelloWorldTrigger作为触发器名称,然后选择sObject的Account。点击 Submit.
  3. 用下面的代码替换默认的代码。
    trigger HelloWorldTrigger on Account (before insert) {
    	System.debug('Hello World!');
    }
  4. 要保存,请按Ctrl + S。
  5. 要测试触发器,请创建一个帐户。
    1. 单击 Debug | Open Execute Anonymous Window.
    2. 在新窗口中,添加以下内容,然后单击 Execute.
      Account a = new Account(Name='Test Trigger');
      insert a;
  6. 在调试日志中,找到Hello World!声明。日志还显示触发器已被执行。

触发器的类型

有两种类型的触发器。

  • 触发器在保存到数据库之前用于更新或验证记录值之前。
  • 使用触发器访问由系统设置的字段值(例如记录的Id或LastModifiedDate字段),并影响其他记录中的更改。触发后触发器的记录是只读的。

使用上下文变量

要访问导致触发器触发的记录,请使用上下文变量。例如,Trigger.New包含插入或更新触发器中插入的所有记录。 Trigger.Old在更新触发器中更新之前提供旧版本的sObjects,或在删除触发器中删除sObjects的列表。当插入一个记录时,或者通过API或Apex批量插入大量记录时,触发器可以触发。因此,上下文变量(如Trigger.New)只能包含一条记录或多条记录。您可以遍历Trigger.New来获取每个单独的sObject。

这个例子是HelloWorldTrigger示例触发器的修改版本。它遍历for循环中的每个帐户并更新每个帐户的说明字段。

trigger HelloWorldTrigger on Account (before insert) {
    for(Account a : Trigger.New) {
        a.Description = 'New description';
    }   
}

注意

系统在触发器执行完成后保存触发前触发器的记录。您可以修改触发器中的记录,而无需显式调用DML插入或更新操作。如果您在这些记录上执行DML语句,则会出现错误。

一些其他的上下文变量返回一个布尔值来指示触发器是否由于更新或其他事件而被触发。当触发器组合多个事件时,这些变量很有用。例如:

trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }        
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}

触发上下文变量

下表是可用于触发器的所有上下文变量的综合列表。

变量 用法
isExecuting 如果Apex代码的当前上下文是触发器,而不是Visualforce页面,Web服务或executeanonymous()API调用,则返回true。
isInsert 如果由于Salesforce用户界面,Apex或API的插入操作而触发此触发器,则返回true。
isUpdate 如果由于Salesforce用户界面,Apex或API的更新操作而触发此触发器,则返回true。
isDelete 如果由于Salesforce用户界面,Apex或API的删除操作而触发此触发器,则返回true。
isBefore 如果在保存任何记录之前触发此触发器,则返回true。
isAfter 如果在保存所有记录后触发此触发器,则返回true。
isUndelete 如果在从回收站中恢复记录(即从Salesforce用户界面,Apex或API取消删除操作之后)触发此触发器,则返回true。
new 返回sObject记录的新版本列表。
此sObject列表仅在插入,更新和取消删除触发器中可用,并且记录只能在before触发器中修改。
newMap ID对新对象记录的版本的映射。
此映射仅在更新之前,插入之后,更新之后和取消删除触发器之后可用。
old 返回sObject记录的旧版本列表。
该sObject列表仅在更新和删除触发器中可用。
oldMap 旧版本的sObject记录的ID映射。
此映射仅在更新和删除触发器中可用。
size 触发器调用中的记录总数,包括旧的和新的。

从触发器调用类方法

您可以通过触发器调用公用事业方法。调用其他类的方法可以重用代码,减少触发器的大小,并且可以提高Apex代码的维护。它也允许你使用面向对象的编程。

以下示例触发器显示如何从触发器调用静态方法。如果由于插入事件触发了该触发器,则此示例将在EmailManager类上调用静态sendMail()方法。此实用程序方法向指定的收件人发送电子邮件,并包含插入的联系人记录数。

注意

EmailManager类包含在“Apex单元入门”的示例中。您必须保存组织中的EmailManager类,并在保存此触发器之前将sendMail()方法更改为static。

  1. 在开发者控制台中,点击 File | New | Apex Trigger.
  2. 输入ExampleTrigger作为触发器名称,然后选择Contact作为sObject。点击 Submit.
  3. 将默认代码替换为以下内容,然后将sendMail()中的电子邮件地址占位符文本修改为您的电子邮件地址。
    trigger ExampleTrigger on Contact (after insert, after delete) {
        if (Trigger.isInsert) {
            Integer recordCount = Trigger.New.size();
            // 从另一个类调用实用程序方法
            EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', 
                        recordCount + ' contact(s) were inserted.');
        }
        else if (Trigger.isDelete) {
            // 删除后的处理
        }
    }
  4. 要保存,请按Ctrl + S。
  5. 要测试触发器,请创建一个联系人。
    1. 单击 Debug | Open Execute Anonymous Window.
    2. 在新窗口中,添加以下内容,然后单击 Execute.
      Contact c = new Contact(LastName='Test Contact');
      insert c;
  6. 在调试日志中,检查触发器是否被触发。在日志末尾,找到实用程序方法编写的调试消息: DEBUG|Email sent successfully
  7. 现在检查您是否收到一封电子邮件,其中正文 1 contact(s) were inserted.

    使用新的触发器,每次添加一个或多个联系人时都会收到一封电子邮件!

添加相关记录

触发器通常用于访问和管理与触发器上下文中的记录相关的记录 – 触发该触发器的记录。

如果没有机会与客户相关联,则此触发器为每个新的或更新的客户添加相关机会。触发器首先执行SOQL查询,以获取触发器触发的帐户的所有子机会。接下来,触发器遍历Trigger.New中的sObjects列表以获取每个帐户的sObject。如果该帐户没有任何相关的机会sObjects,for循环会创建一个。如果触发器创造了新的机会,最后的陈述将插入它们。

  1. 使用开发者控制台添加以下触发器(按照HelloWorldTrigger示例的步骤,但使用AddRelatedRecord作为触发器名称)。
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        
        // 获取这个触发器中客户的相关机会
        Map<Id,Account> acctsWithOpps = new Map<Id,Account>(
            [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.New]);
        
        // 为每个帐户添加一个机会,如果它还没有。
        // 遍历每个帐户。
        for(Account a : Trigger.New) {
            System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size());
            // 检查帐号是否有相关的机会。
            if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) {
                // 如果没有,请添加一个默认机会
                oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                           StageName='Prospecting',
                                           CloseDate=System.today().addMonths(1),
                                           AccountId=a.Id));
            }           
        }
    
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. 要测试触发器,请在Salesforce用户界面中创建一个帐户,并将其命名为Apple和Orange。
  3. 在客户页面的机会相关列表中,找到新的机会。触发器自动添加了这个机会!

超越基础

你添加的触发器迭代所有属于触发器上下文的记录 – for循环遍历Trigger.New。但是,这个触发器中的循环可能更有效率。我们并不是真的需要访问这个触发器上下文中的每个客户,而是只有一个子集 – 没有机会的客户。下一个单元显示如何使这个触发器更有效率。在“批量触发器设计模式”单元中,了解如何修改SOQL查询以仅获取没有机会的帐户。然后,学习只遍历这些记录。

使用触发器异常

您有时需要对某些数据库操作添加限制,例如在满足某些条件时防止保存记录。为了防止在触发器中保存记录,在有问题的sObject上调用addError()方法。 addError()方法在触发器中引发致命错误。错误消息显示在用户界面中并被记录。

如果具有相关机会,则以下触发器可防止删除帐户。默认情况下,删除一个帐户会导致所有相关记录的级联删除。这个触发器可以防止级联删除机会。为自己尝试这个触发器!如果你已经执行了前面的例子,你的组织有一个名为苹果和橙子的客户有一个相关的机会。本示例使用该示例帐户。

  1. 使用开发者控制台添加以下触发器。
    trigger AccountDeletion on Account (before delete) {
       
        // 如果他们有相关的机会,防止删除帐户。
        for (Account a : [SELECT Id FROM Account
                         WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                         Id IN :Trigger.old]) {
            Trigger.oldMap.get(a.Id).addError(
                '不能删除有相关机会的帐号');
        }
        
    }
  2. 在Salesforce用户界面中,导航到Apples&Oranges帐户的页面,然后单击 Delete.
  3. 在确认弹出窗口中,单击 OK.

    使用自定义错误消息查找验证错误无法删除具有相关机会的帐户。

  4. 禁用AccountDeletion触发器。如果你保持这个触发器激活,你不能检查你的挑战。
    1. 从设置中搜索Apex触发器。
    2. 在Apex触发器页面上,单击AccountDeletion触发器旁边的编辑。
    3. 取消选择 Is Active.
    4. 点击 Save.

超越基础

在触发器中调用addError()会导致整个操作集回滚,除非部分成功调用批量DML。

  • 如果Apex中的DML语句产生了触发器,则任何错误都会回滚整个操作。但是,运行时引擎仍会处理操作中的每个记录,以编译一个全面的错误列表。
  • 如果Force.com API中的批量DML调用产生了触发器,那么运行时引擎将把有害的记录放在一边。运行时引擎然后尝试部分保存没有产生错误的记录。

触发器和外调

Apex允许您打电话并将Apex代码与外部Web服务集成。 Apex调用外部Web服务称为标注。例如,您可以调出股票报价服务以获取最新的报价。当从触发器中进行标注时,标注必须异步完成,以便在等待外部服务的响应时触发器不会阻止您的工作。异步标注在后台进程中进行,并在收到响应时外部服务返回它。

要从触发器中进行调用,请调用异步执行的类方法。这种方法被称为未来的方法,并用@future(callout = true)进行注释。此示例类包含制作标注的未来方法。

注意

该示例仅为说明目的使用了假设的端点URL。除非您将端点更改为有效的URL并在Salesforce中为您的端点添加远程站点,否则无法运行此示例。

public class CalloutClass {
    @future(callout=true)
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // 设置端点URL。
        String endpoint = 'http://yourHost/yourService';
        request.setEndPoint(endpoint);
        // 将HTTP动词设置为GET。
        request.setMethod('GET');
        // 发送HTTP请求并获得响应。
        HttpResponse response = new HTTP().send(request);
    }
}

这个例子展示了调用类中的方法来异步调用标注的触发器。

trigger CalloutTrigger on Account (before insert, before update) {
    CalloutClass.makeCallout();
}
本部分仅提供了标注的概述,并不打算详细说明标注。有关更多信息,请参阅Apex Developer Guide中的使用Apex调用标注。

Salesforce Apex (基础5)SOSL

学习目标

完成本单元后,您将能够:

  • 描述SOSL和SOQL之间的区别。
  • 使用SOSL查询搜索跨多个对象的字段。
  • 使用开发者控制台中的查询编辑器执行SOSL查询。

编写SOSL查询

Salesforce对象搜索语言(SOSL)是一种Salesforce搜索语言,用于在记录中执行文本搜索。使用SOSL在Salesforce中跨多个标准和自定义对象记录搜索字段。 SOSL类似于Apache Lucene。
向Apex添加SOSL查询非常简单 – 您可以直接在您的Apex代码中嵌入SOSL查询。当SOSL嵌入在Apex中时,它被称为在线SOSL。

这是SOSL查询的一个例子,该查询搜索具有任何字段“SFDC”的客户和联系人。

List<List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS 
                                      RETURNING Account(Name), Contact(FirstName,LastName)];

SOQL与SOSL的差异与相似性

与SOQL一样,SOSL允许您搜索组织的记录以获取特定信息。与一次只能查询一个标准或自定义对象的SOQL不同,单个SOSL查询可以搜索所有对象。

另一个区别是SOSL匹配基于单词匹配的字段,而默认情况下SOQL执行完全匹配(当不使用通配符时)。例如,在SOSL中搜索“Digital”会返回字段值为“Digital”或“The Digital Company”的记录,但SOQL只返回字段值为“Digital”的记录。

SOQL和SOSL是两种不同语法的独立语言。每种语言都有一个独特的用例:

  • 使用SOQL检索单个对象的记录。
  • 使用SOSL跨多个对象搜索字段。 SOSL查询可以搜索对象上的大多数文本字段。

先决条件

本单位的一些查询希望组织拥有账户和联系人。如果您还没有在SOQL单元中创建样本数据,请在本单元中创建样本数据。否则,您可以跳过在本节中创建示例数据。

  1. 在开发人员控制台中,从“调试”菜单中打开“执行匿名”窗口。
  2. 在窗口中插入下面的代码片段,然后点击执行。
// 添加客户和相关联系人
Account acct = new Account(
    Name='SFDC Computing',
    Phone='(415)555-1212',
    NumberOfEmployees=50,
    BillingCity='San Francisco');
insert acct;

// 一旦插入客户,sObject将填充一个ID。
// 获取这个ID
ID acctID = acct.ID;

// 添加一个联系人到这个客户。
Contact con = new Contact(
    FirstName='Carol',
    LastName='Ruiz',
    Phone='(415)555-1212',
    Department='Wingo',
    AccountId=acctID);
insert con;

// 添加没有联系人的客户
Account acct2 = new Account(
    Name='The SFDC Query Man',
    Phone='(310)555-1213',
    NumberOfEmployees=50,
    BillingCity='Los Angeles',
    Description='Expert in wing technologies.');
insert acct2;

使用查询编辑器

开发者控制台提供查询编辑器控制台,使您可以运行SOSL查询和查看结果。查询编辑器提供了一种快速检查数据库的方法。在将它们添加到您的Apex代码之前,测试您的SOSL查询是一个好方法。当您使用查询编辑器时,您只需提供没有围绕它的Apex代码的SOSL语句。

让我们尝试运行以下SOSL示例:

  1. 在开发者控制台中,点击 Query Editor 标签。
  2. 将以下内容复制并粘贴到查询编辑器下的第一个框中,然后单击 Execute.
FIND {Wingo} IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName,Department)
您组织中符合标准的所有客户和联系人记录都将显示在“查询结果”部分中作为包含字段的行。结果按每个对象(客户或联系人)的选项卡进行分组。 SOSL查询返回具有与Wingo匹配的字段的记录。根据我们的样本数据,只有联系人具有Wingo值的字段,因此此联系人将被返回。

注意

查询编辑器和API中的搜索查询必须用大括号({Wingo})括起来。相反,在Apex中,搜索查询包含在单引号(’Wingo’)内。

基本的SOSL语法

SOSL允许您指定以下搜索条件:

  • 文本表达(单个单词或短语)来搜索
  • 要搜索的字段的范围
  • 要检索的对象和字段的列表
  • 在源对象中选择行的条件

这是一个基本的SOSL查询语法:

FIND 'SearchQuery' [IN SearchGroup] [RETURNING ObjectsAndFields]

SearchQuery是要搜索的文本(单个单词或短语)。搜索项可以与逻辑运算符(AND,OR)和括号组合。此外,搜索字词可以包含通配符(*,?)。 *通配符在搜索项的中间或末尾匹配零个或多个字符。这个?通配符只匹配搜索项中间或末尾的一个字符。

文本搜索不区分大小写。例如,搜索客户,客户或CUSTOMER都会返回相同的结果。

SearchGroup是可选的。这是搜索领域的范围。如果未指定,则默认搜索范围是所有字段。 SearchGroup可以采取以下值之一。

  • ALL FIELDS
  • NAME FIELDS
  • EMAIL FIELDS
  • PHONE FIELDS
  • SIDEBAR FIELDS

ObjectsAndFields是可选的。它是在搜索结果中返回的信息 – 一个或多个sObjects的列表,并且在每个sObject中,包含一个或多个字段的列表,并带有可筛选的值。如果未指定,则搜索结果包含找到的所有对象的ID。

单词和短语

SearchQuery包含两种类型的文本:

  • Single Word— 单个单词,如test或hello。 SearchQuery中的单词由空格,标点符号和从字母到数字的变化(反之亦然)分隔。单词总是不区分大小写的。
  • Phrase— 由双引号包围的单词和空格的集合,如“约翰史密斯”。多个单词可以与逻辑和分组操作符组合在一起,形成一个更复杂的查询。

搜索示例

要了解SOSL搜索的工作原理,让我们使用不同的搜索字符串来查看输出是基于我们的样本数据。此表列出了各种示例搜索字符串和SOSL搜索结果。

在所有领域搜索: 搜索说明 匹配的记录和字段
查询 此搜索将返回其所有字段都包含文本的所有记录:The和Query。搜索字词中的单词顺序无关紧要。 客户:SFDC查询人(名称字段匹配)
Wingo OR Man 该搜索使用OR逻辑运算符。它返回包含Wingo单词的字段的记录或包含SFDC单词的字段的记录。 联系人:Carol Ruiz,部门:’Wingo’
客户:SFDC查询人(名称字段匹配)
1212 此搜索将返回其所有字段包含1212字的记录。以-1212结尾的电话字段被匹配,因为1212被短划线分隔时被认为是单词。客户:SFDC查询人,电话:’(415)555-1212′ 联系人:Carol Ruiz,电话:’(415)555-1212′
wing* 这是一个通配符搜索。此搜索将返回具有以wing开头的字段值的所有记录。

账号:SFDC查询员,描述: ‘Expert in wing technologies.’

SOSL Apex示例

此示例显示如何在Apex中运行SOSL查询。这个SOSL查询通过使用OR逻辑运算符来组合两个搜索项 – 它在任何字段中搜索Wingo或SFDC。此示例返回所有示例客户,因为它们每个都有一个包含其中一个单词的字段。 SOSL搜索结果以列表形式返回。每个列表包含返回记录的数组。在这种情况下,列表有两个元素。在索引0处,列表包含客户数组。在索引1处,列表包含联系人数组。

在开发者控制台的“执行匿名”窗口中执行此代码段。接下来,检查调试日志以验证是否返回所有记录。

List<List<sObject>> searchList = [FIND 'Wingo OR SFDC' IN ALL FIELDS 
                   RETURNING Account(Name),Contact(FirstName,LastName,Department)];
Account[] searchAccounts = (Account[])searchList[0];
Contact[] searchContacts = (Contact[])searchList[1];

System.debug('Found the following accounts.');
for (Account a : searchAccounts) {
    System.debug(a.Name);
}

System.debug('Found the following contacts.');
for (Contact c : searchContacts) {
    System.debug(c.LastName + ', ' + c.FirstName);
}

告诉我更多…

您可以筛选,重新排序和限制SOSL查询的返回结果。因为SOSL查询可以返回多个sObjects,所以这些过滤器应用在RETURNING子句中的每个sObject中。

您可以通过在对象的WHERE子句中添加条件来过滤SOSL结果。例如,这只会导致其行业服装被退回的客户:退货客户(名称,行业)行业=’服装’)

同样,通过为一个对象添加ORDER BY来支持一个sObject的排序结果。例如,这会导致返回的客户按名称字段排序:RETURNING客户(名称,行业ORDER BY名称)。

返回记录的数量可以限制为记录的一个子集。此示例将返回的客户限制为10个:RETURNING Account(Name,Industry LIMIT 10)

Salesforce Apex (基础4)SOQL

学习目标

完成本单元后,您将能够:

  • 在Apex中编写SOQL查询。
  • 通过在开发者控制台中使用查询编辑器执行SOQL查询。
  • 通过使用匿名Apex执行嵌入在Apex中的SOQL查询。
  • 查询相关记录。

编写SOQL查询

要从Salesforce中读取记录,您需要编写一个查询。 Salesforce提供Salesforce对象查询语言(简称SOQL),您可以使用它来读取保存的记录。 SOQL类似于标准的SQL语言,但是为Force.com平台定制。
由于Apex可直接访问存储在数据库中的Salesforce记录,因此可以将SOQL查询嵌入到Apex代码中,并以简单明了的方式获取结果。当SOQL嵌入到Apex中时,它被称为内联SOQL。

要在您的Apex代码中包含SOQL查询,请将SOQL语句包装在方括号中,并将返回值分配给一个sObjects数组。例如,以下内容将检索具有两个字段(名称和电话号码)的所有客户记录,并返回一个Account sObjects数组。

Account[] accts = [SELECT Name,Phone FROM Account];

先决条件

本单位的一些查询希望组织拥有客户和联系人。在运行查询之前,请创建一些示例数据。

  1. 在开发人员控制台中,从“调试”菜单中打开“执行匿名”窗口。
  2. 在窗口中插入下面的代码片段,然后点击执行。
// 添加客户和相关联系人
Account acct = new Account(
    Name='SFDC Computing',
    Phone='(415)555-1212',
    NumberOfEmployees=50,
    BillingCity='San Francisco');
insert acct;

// 一旦插入客户,sObject将填充一个ID。
// 获取这个ID。
ID acctID = acct.ID;

// 添加一个联系人到这个客户。
Contact con = new Contact(
    FirstName='Carol',
    LastName='Ruiz',
    Phone='(415)555-1212',
    Department='Wingo',
    AccountId=acctID);
insert con;

// 添加没有联系人的客户
Account acct2 = new Account(
    Name='The SFDC Query Man',
    Phone='(310)555-1213',
    NumberOfEmployees=50,
    BillingCity='Los Angeles',
    Description='Expert in wing technologies.');
insert acct2;

使用查询编辑器

开发者控制台提供查询编辑器控制台,使您可以运行您的SOQL查询和查看结果。查询编辑器提供了一种快速检查数据库的方法。在将它们添加到您的Apex代码之前,测试您的SOQL查询是一个好方法。当您使用查询编辑器时,您只需提供SOQL语句,而不包含围绕它的Apex代码。

让我们尝试运行以下SOQL示例:

  1. 在开发者控制台中,点击 Query Editor 标签。
  2. 将以下内容复制并粘贴到查询编辑器下的第一个框中,然后单击 Execute.
SELECT Name,Phone FROM Account

您的组织中的所有客户记录都以查询结果部分显示为包含字段的行。

基本的SOQL语法

这是一个基本的SOQL查询语法:

SELECT fields FROM ObjectName [WHERE Condition]
WHERE子句是可选的。我们从一个非常简单的查询开始。例如,以下查询检索客户并为每个客户获取两个字段:ID和电话。
SELECT Name,Phone FROM Account

查询有两部分:

  1. 选择名称,电话:这部分列出您想要检索的字段。这些字段在逗号分隔列表中的SELECT关键字之后指定。或者您只能指定一个字段,在这种情况下不需要逗号(例如SELECT Phone)。
  2. FROM客户:这部分指定您要检索的标准或自定义对象。在这个例子中,它是客户。对于名为Invoice_Statement的自定义对象,它是Invoice_Statement__c。

超越基础

与其他SQL语言不同,您不能为所有字段指定*。你必须指定你想明确得到的每个字段。如果您尝试访问您在SELECT子句中未指定的字段,则会出现错误,因为该字段尚未被检索到。

您不需要在查询中指定Id字段,因为它始终在Apex查询中返回,无论是在查询中指定还是不在查询中指定。例如:SELECT Id,Phone FROM Account和SELECT Phone FROM Account是等价的语句。唯一一次你可能想要指定Id字段,如果它是唯一的字段,你正在检索,因为你必须列出至少一个字段:SELECT ID FROM Account。在查询编辑器中运行查询时,您可能还需要指定Id字段,因为除非指定,否则ID字段将不会显示。

使用条件过滤查询结果

如果您在组织中拥有多个客户,则将全部退回。如果要限制返回给满足特定条件的客户的客户,则可以在WHERE子句中添加此条件。以下示例仅检索名称为SFDC Computing的客户。请注意,字符串比较不区分大小写。

SELECT Name,Phone FROM Account WHERE Name='SFDC Computing'
WHERE子句可以包含多个使用逻辑运算符(AND,OR)和括号分组的条件。例如,此查询将返回名称为SFDC Computing的拥有超过25名员工的所有客户:
SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' AND NumberOfEmployees>25)
这是一个更复杂条件的例子。此查询将返回所有SFDC计算客户,或者拥有超过25名员工的帐单城市为洛杉矶的所有客户。
SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' OR (NumberOfEmployees>25 AND BillingCity='Los Angeles'))

超越基础

您可以使用LIKE运算符执行模糊匹配,而不是使用相等运算符(=)进行比较。例如,您可以使用以下条件检索名称以SFDC开头的所有客户:WHERE Name LIKE’SFDC%’。 %通配符匹配任何字符或不匹配。相反的_字符可以用来匹配一个字符。

订购查询结果

执行查询时,它将从Salesforce返回记录,但不会按特定顺序进行,因此每次运行查询时都不能依赖返回数组中记录的顺序。但是,您可以选择通过添加ORDER BY子句并指定记录集应该排序的字段来对返回的记录集进行排序。本示例根据“名称”字段对所有检索到的客户进行排序。

SELECT Name,Phone FROM Account ORDER BY Name
默认排序顺序按照字母顺序排列,指定为ASC。以前的声明相当于:
SELECT Name,Phone FROM Account ORDER BY Name ASC
要反转订单,请按降序使用DESC关键字:
SELECT Name,Phone FROM Account ORDER BY Name DESC

您可以对大多数字段进行排序,包括数字和文本字段。你不能在丰富的文本和多选择选项列表上排序。

在“查询编辑器”中尝试使用这些SOQL语句,并根据“名称”字段查看返回记录的顺序如何变化。

限制返回的记录数

您可以通过添加LIMIT n子句来限制返回到任意数字的记录数,其中n是要返回的记录数。当你不关心哪些记录被返回时,限制结果集很方便,但是你只想使用记录的一个子集。例如,此查询检索返回的第一个客户。请注意,使用LIMIT 1时,返回的值是一个客户,而不是数组。

Account oneAccountOnly = [SELECT Name,Phone FROM Account LIMIT 1];

把所有的东西结合在一起

您可以按照以下顺序在一个查询中组合可选子句

SELECT Name,Phone FROM Account 
                   WHERE (Name = 'SFDC Computing' AND NumberOfEmployees>25)
                   ORDER BY Name
                   LIMIT 10
在开发人员控制台中使用“执行匿名”窗口在Apex中执行以下SOQL查询。然后检查调试日志中的调试语句。应该返回一个样本客户。
Account[] accts = [SELECT Name,Phone FROM Account 
                   WHERE (Name='SFDC Computing' AND NumberOfEmployees>25)
                   ORDER BY Name
                   LIMIT 10];
System.debug(accts.size() + ' account(s) returned.');
// 写入所有客户数组信息
System.debug(accts);

在SOQL查询中访问变量

Apex中的SOQL语句可以引用Apex代码变量和表达式,前提是冒号(:)。在SOQL语句中使用局部变量称为绑定。

这个例子展示了如何在WHERE子句中使用targetDepartment变量。

String targetDepartment = 'Wingo';
Contact[] techContacts = [SELECT FirstName,LastName 
                          FROM Contact WHERE Department=:targetDepartment];

查询相关记录

Salesforce中的记录可以通过关系相互关联:查找关系或主 – 细节关系。例如,联系人与“客户”具有查找关系。当您创建或更新联系人时,您可以将其与一个客户相关联。与相同客户关联的联系人显示在客户页面的相关列表中。以同样的方式,您可以查看Salesforce用户界面中的相关记录,您可以在SOQL中查询相关记录。

要获取与父记录相关的子记录,请为子记录添加一个内部查询。内部查询的FROM子句针对关系名称而不是Salesforce对象名称。此示例包含一个内部查询以获取与每个返回的客户关联的所有联系人。 FROM子句指定联系人关系,这是客户与客户和联系人链接的默认关系。

SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing'

下一个示例在Apex中嵌入示例SOQL查询,并演示如何使用sObject上的Contacts关系名称从SOQL结果中获取子记录。

Account[] acctsWithContacts = [SELECT Name, (SELECT FirstName,LastName FROM Contacts)
                               FROM Account 
                               WHERE Name = 'SFDC Computing'];
// 获取子记录
Contact[] cts = acctsWithContacts[0].Contacts;
System.debug('Name of first associated contact: ' 
             + cts[0].FirstName + ', ' + cts[0].LastName);
您可以使用点符号从子对象(联系人)到其父级(Account.Name)上的字段进行遍历。例如,以下Apex片段查询联系名字为Carol的记录,并且能够通过遍历客户和联系人之间的关系来检索Carol的相关客户的名称。
Contact[] cts = [SELECT Account.Name FROM Contact 
                 WHERE FirstName = 'Carol' AND LastName='Ruiz'];
Contact carol = cts[0];
String acctName = carol.Account.Name;
System.debug('Carol\'s account name is ' + acctName);

注意

本节中的示例基于标准对象。自定义对象也可以通过使用自定义关系链接在一起。自定义关系名称以__r后缀结尾。例如,发票声明通过Invoice_Statement__c自定义对象上的Line_Items__r关系链接到订单项。 (这些自定义对象是Force.com工作簿中使用的Warehouse模式的一部分。)

通过使用SOQL for循环查询批次中的记录

使用SOQL for循环,可以在for循环中包含SOQL查询。 SOQL查询的结果可以在循环内迭代。 SOQL for循环使用不同的方法来检索记录 – 通过调用SOAP API的查询和queryMore方法,使用高效分块来检索记录。通过使用SOQL for循环,可以避免碰到堆大小的限制。

SOQL for循环遍历由SOQL查询返回的所有sObject记录。 SOQL for循环的语法是:

for (variable : [soql_query]) {
    code_block
}
要么
for (variable_list : [soql_query]) {
    code_block
}

变量和variable_list必须与soql_query返回的sObjects类型相同。

最好使用SOQL for循环的sObject列表格式,因为循环为每个200 sObjects批次执行一次。这样做使您可以批量处理记录并批量执行DML操作,这有助于避免达到管理员限制。

insert new Account[]{new Account(Name = 'for loop 1'), 
                     new Account(Name = 'for loop 2'), 
                     new Account(Name = 'for loop 3')};

// sObject列表格式为每个返回的批记录执行一次for循环
Integer i=0;
Integer j=0;
for (Account[] tmp : [SELECT Id FROM Account WHERE Name LIKE 'for loop _']) {
    j = tmp.size();
    i++;
}
System.assertEquals(3, j); // 列表中应该包含三个名为“yyy”的客户
System.assertEquals(1, i); // 由于一个批次最多可以容纳200条记录,只有三条记录应该被返回,所以循环只能执行一次

Salesforce Apex (基础3)操作记录

学习目标

完成本单元后,您将能够:

  • 使用DML插入,更新和删除记录。
  • 批量执行DML语句。
  • 使用upsert来插入或更新记录。
  • 捕获一个DML异常。
  • 使用数据库方法插入具有部分成功选项的新记录并处理结果。
  • 知道何时使用DML语句以及何时使用数据库方法。
  • 对相关记录执行DML操作

用DML操作记录

使用数据操作语言(简称DML)在Salesforce中创建和修改记录。 DML通过提供简单的语句来插入,更新,合并,删除和恢复记录,提供了一种直接管理记录的方法。
由于Apex是一种以数据为中心的语言,并保存在Force.com平台上,因此可直接访问Salesforce中的数据。与其他需要额外设置以连接数据源的编程语言不同,使用Apex DML,管理记录变得非常简单!通过调用DML语句,您可以快速对Salesforce记录执行操作。

此示例将Acme客户添加到Salesforce。首先创建一个客户sObject,然后将其作为参数传递给insert语句,该语句在Salesforce中保留该记录。

// 创建客户sObject
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
// 通过使用DML插入客户
insert acct;

DML语句

以下DML语句可用。

  • insert
  • update
  • upsert
  • delete
  • undelete
  • merge

每个DML语句接受一个单独的sObject或一个sObject的列表(或数组)。在sObjects列表上操作是处理记录的更有效的方法。

除了一对,所有这些陈述都是熟悉的数据库操作。 upsert和merge语句是Salesforce特有的,可以非常方便。

upsert DML操作创建新记录,并在单个语句中更新sObject记录,使用指定的字段确定现有对象的存在,或者在没有指定字段的情况下使用ID字段。

合并语句将最多三个相同sObject类型的记录合并到其中一个记录中,删除其他记录,并将所有相关记录重新归类。

ID字段自动分配到新记录

插入记录时,系统为每个记录分配一个ID。除了在数据库中保存ID值之外,还可以在用作DML调用中的参数的sObject变量上自动填充ID值。

这个例子展示了如何获得对应于插入客户的sObject的ID。

// 创建客户sObject
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
// 通过使用DML插入客户
insert acct;

// 获取插入的sObject参数的新ID
ID acctID = acct.Id;
// 在调试日志中显示此ID
System.debug('ID = ' + acctID);

// 调试日志结果(在您的情况下ID将不同)
// DEBUG|ID = 001D000000JmKkeIAF

超越基础

由于示例中的sObject变量包含DML调用后的ID,因此您可以重新使用此sObject变量来执行进一步的DML操作(如更新),因为系统可以通过匹配ID来将sObject变量映射到其对应的记录。

您可以从数据库检索记录以获取其字段,包括ID字段,但这不能用DML完成。您需要使用SOQL编写查询。您将在另一个单元学习SOQL。

批量 DML

您可以在单个sObject上执行DML操作,也可以在sObjects列表上批量执行DML操作。执行批量DML操作是推荐的方法,因为它有助于避免触及限额,例如每个Apex交易的DML限制为150条语句。这个限制是为了确保公平地访问Force.com多租户平台中的共享资源。在sObjects列表上执行DML操作会计算为列表中所有sObjects的一个DML语句,而不是每个sObject的一个语句。

此示例通过在一次调用中插入联系人列表来批量插入联系人。该示例然后批量更新这些联系人。

  1. 在开发者控制台中使用Anonymous Apex执行此代码段。
    // 创建联系人列表
    List<Contact> conList = new List<Contact> {
        new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
            new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
            new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
            new Contact(FirstName='Kim',LastName='Shain',Department='Education')};
                
    // 用一个DML调用批量插入所有联系人
    insert conList;
    
    // List来保存新的联系人进行更新
    List<Contact> listToUpdate = new List<Contact>();
    
    // 遍历列表并仅添加标题
    //  如果部门是财务部门
    for(Contact con : conList) {
        if (con.Department == 'Finance') {
            con.Title = 'Financial analyst';
            // Add updated contact sObject to the list.
            listToUpdate.add(con);
        }
    }
    
    // 将更新的联系人sObject添加到列表中。
    update listToUpdate;
    
  2. 检查最近在您的组织中创建的联系人。
    两位在财务部门的联系人应该有他们的头衔,财务分析师。

插入记录

如果您有一个包含新记录和现有记录混合的列表,则可以使用upsert语句来处理对列表中所有记录的插入和更新。 Upsert有助于避免重复记录的创建,并且可以节省时间,因为您不必先确定哪些记录存在。

upsert语句通过比较一个字段的值来将sObjects与现有记录进行匹配。如果调用此语句时未指定字段,则upsert语句将使用sObject的ID将sObject与Salesforce中的现有记录进行匹配。或者,您可以指定一个字段用于匹配。对于自定义对象,请指定标记为外部标识的自定义字段。对于标准对象,可以指定任何idLookup属性设置为true的字段。例如,联系人或用户的电子邮件字段设置了idLookup属性。要检查字段的属性,请参阅Salesforce和Force.com的对象参考。

Upsert语法

upsert sObject | sObject[]

upsert sObject | sObject[]​​ field

可选字段是字段标记。例如,要指定MyExternalID字段,该语句是:

upsert sObjectList Account.Fields.MyExternalId;

Upsert使用sObject记录的主键(ID),idLookup字段或外部ID字段来确定是否应该创建新记录或更新现有记录:

  • 如果密钥不匹配,则创建一个新的对象记录。
  • 如果密钥匹配一次,则更新现有对象记录。
  • 如果密钥匹配多次,则会生成错误,并且不会插入或更新对象记录。

这个例子显示了upsert如何更新一个现有的联系人记录,并在一个呼叫中插入一个新的联系人。这个upsert调用更新现有的Josh联系人并插入一个新的联系人Kathy。

注意

upsert调用使用该ID来匹配第一个联系人。 josh变量正被重新用于upsert调用。这个变量已经被填充了上一次插入调用的记录ID,所以在这个例子中不需要显式地设置ID。

  1. 在开发者控制台的“执行匿名”窗口中执行此代码段。
    //插入Josh联系人
    Contact josh = new Contact(FirstName='Josh',LastName='Kaplan',Department='Finance');       
    insert josh;
    
    // Josh's 的记录已被插入
    //   所以变量josh现在有一个ID
    //   将用于通过upsert匹配记录
    josh.Description = 'Josh\'s record has been updated by the upsert operation.';
    
    // 创建Kathy联系人,但不要将其保存在数据库中
    Contact kathy = new Contact(FirstName='Kathy',LastName='Brown',Department='Technology');
    
    // 列举以保持新联系人upsert
    List<Contact> contacts = new List<Contact> { josh, kathy };
    
    // 调用upsert
    upsert contacts;
    
    // 结果:Josh被更新,Kathy被创建。
  2. 检查组织中的所有联系人。
    您的组织只有一个Josh Kaplan记录,而不是两个,因为upsert操作找到了现有记录并进行了更新,而不是创建新的联系人记录。凯西·布朗的一个联系记录也将在那里。或者,您可以指定一个字段用于匹配记录。此示例使用Contact上的电子邮件字段,因为它具有idLookup属性集。该示例插入Jane Smith联系人,并创建第二个Contact sObject,使用相同的电子邮件填充它,然后使用电子邮件字段进行匹配以调用upsert以更新联系人。

注意

如果在这个例子中使用了插入而不是upsert,那么将会插入一个重复的Jane Smith联系人。

  1. 在开发者控制台的“执行匿名”窗口中执行此代码段。
    Contact jane = new Contact(FirstName='Jane',
                             LastName='Smith',
                             Email='jane.smith@example.com',
                             Description='Contact of the day');
    insert jane;
    
    // 1.使用idLookup字段的Upsert
    // 创建第二个sObject变量。
    // 这个变量没有任何ID设置。
    Contact jane2 = new Contact(FirstName='Jane',
                             LastName='Smith',  
                             Email='jane.smith@example.com',
                             Description='Prefers to be contacted by email.');
    // 通过使用idLookup字段进行匹配来提升联系人。
    upsert jane2 Contact.fields.Email;
    
    // 确认联系人已更新
    System.assertEquals('Prefers to be contacted by email.',
                       [SELECT Description FROM Contact WHERE Id=:jane.Id].Description);
    
  2. 检查组织中的所有联系人。

    您的组织将只有一个Jane Smith联系更新后的描述。

删除记录

您可以使用delete语句删除持久记录。删除的记录不会从Force.com永久删除,但是它们可以从恢复位置放置在回收站中15天。

此示例显示如何删除姓氏为Smith的所有联系人。如果您已经为批量DML运行了样本,那么您的组织应该与Smith的姓氏有两个联系。在开发人员控制台中使用匿名Apex执行此代码段,然后验证是否没有与姓氏Smith的联系人。

Contact[] contactsDel = [SELECT Id FROM Contact WHERE LastName='Smith']; 
delete contactsDel;

注意

这个片段包含一个查询来检索联系人(一个SOQL查询)。你会在另一个单元学到更多关于SOQL的知识。

DML语句异常

如果DML操作失败,则返回DmlException类型的异常。您可以在代码中捕获异常以处理错误情况。

这个例子产生了一个DmlException,因为它试图插入一个没有必需的Name字段的客户。 catch块中捕获到异常。

try {
    // 这会导致异常
    //   不提供必需的名称字段。
    Account acct = new Account();
    // 插入客户
    insert acct;
} catch (DmlException e) {
    System.debug('A DML exception has occurred: ' +
                e.getMessage());
}

数据库方法

Apex包含内置的Database类,它提供执行DML操作和镜像DML语句对应的方法。
这些数据库方法是静态的,并在类名称上调用。
  • Database.insert()
  • Database.update()
  • Database.upsert()
  • Database.delete()
  • Database.undelete()
  • Database.merge()

与DML语句不同,数据库方法有一个可选的allOrNone参数,允许您指定操作是否应该部分成功。当此参数设置为false时,如果在部分记录集上发生错误,则成功的记录将被提交,并且将为失败的记录返回错误。另外,部分成功选项也不会引发异常。

这是你如何调用allOrNone设置为false的插入方法。

Database.insert(recordList, false);
Database方法返回包含每条记录的成功或失败信息的结果对象。例如,insert和update操作每个都返回一个Database.SaveResult对象的数组。
Database.SaveResult[] results = Database.insert(recordList, false);

注意

Upsert返回Database.UpsertResult对象,并删除返回的Database.DeleteResult对象。

默认情况下,allOrNone参数为true,这意味着Database方法的行为与其DML语句相对应,如果遇到故障,则会引发异常。

以下两条语句相当于insert recordList;声明。

Database.insert(recordList);
和:
Database.insert(recordList, true);

超越基础

除了这些方法外,Database类还包含不作为DML语句提供的方法。例如,用于事务控制和回滚的方法,用于清空回收站以及与SOQL查询相关的方法。您将在另一个单元学习SOQL。

示例:以部分成功插入记录

我们来看看使用数据库方法的例子。此示例基于批量DML示例,但是用Database方法替换了DML语句。用部分成功选项调用Database.insert()方法。列表中的一个联系人没有任何有意使用的字段,并且会导致错误,因为如果没有所需的姓氏字段,则无法保存联系人。三个联系人被提交,没有任何字段的联系人会产生错误。本示例的最后一部分遍历返回的结果并将调试消息写入调试日志。

  1. 在开发者控制台的“执行匿名”窗口中执行此示例。
    // 创建联系人列表
    List<Contact> conList = new List<Contact> {
            new Contact(FirstName='Joe',LastName='Smith',Department='Finance'),
            new Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),
            new Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),
            new Contact()};
                
    // 用一个DML调用批量插入所有联系人
    Database.SaveResult[] srList = Database.insert(conList, false);
    
    // 遍历每个返回的结果
    for (Database.SaveResult sr : srList) {
        if (sr.isSuccess()) {
            // 操作成功,所以得到处理记录的ID
            System.debug('Successfully inserted contact. Contact ID: ' + sr.getId());
        } else {
            // 操作失败,所以得到所有的错误
            for(Database.Error err : sr.getErrors()) {
                System.debug('The following error has occurred.');
                System.debug(err.getStatusCode() + ': ' + err.getMessage());
                System.debug('Contact fields that affected this error: ' + err.getFields());
    	 }
        }
    }
    
  2. 验证调试消息(使用过滤器的DEBUG关键字)。
    应该报告一个故障,应该插入三个联系人。

你应该使用DML语句还是数据库方法?

  • 如果希望在批量DML处理期间发生的任何错误作为立即中断控制流的Apex异常(通过使用try。。catch块)引发,请使用DML语句。这种行为类似于大多数数据库过程语言中处理异常的方式。
  • 如果要允许批量DML操作的部分成功,请使用Database类方法 – 如果记录失败,则DML操作的其余部分仍可能成功。然后您的应用程序可以检查被拒绝的记录,并可能重试该操作。使用此表单时,您可以编写不会抛出DML异常错误的代码。相反,您的代码可以使用适当的结果数组来判断成功或失败。请注意,数据库方法还包括一个支持抛出异常的语法,类似于DML语句。

Salesforce Apex (基础2)sObjects

学习目标

完成本单元后,您将能够:

  • 描述sObjects和Salesforce记录之间的关系。
  • 创建并使用特定的sObject变量。
  • 将一个通用的sObject转换为一个特定的sObject。

使用sObjects

由于Apex与数据库紧密集成,因此您可以直接从Apex访问Salesforce记录及其字段。 Salesforce中的每个记录都本地表示为Apex中的sObject。例如,Acme客户记录对应于Apex中的Account sObject。您可以在用户界面中查看和修改的Acme记录的字段也可以在sObject上直接读取和修改。
下表列出了Acme客户示例记录的一些填充字段。 Account sObject是客户记录的抽象,并将客户字段信息作为对象保存在内存中。
表1.检索到的记录的AccountSObject
客户字段
Id 001D000000JlfXe
公司名称 Acme
电话 (415)555-1212
员工人数 100

每个Salesforce记录在插入Salesforce之前都被表示为一个sObject。同样,从Salesforce检索持久性记录时,它们存储在sObject变量中。

Salesforce中的标准和自定义对象记录将映射到Apex中的sObject类型。以下是Apex中用于标准对象的一些常用的sObject类型名称。

  • 客户
  • 联系
  • 潜在客户
  • 机会

如果您在组织中添加了自定义对象,请使用Apex中自定义对象的API名称。例如,名为Merchandise的自定义对象对应于Apex中的Merchandise__c sObject。

创建sObject变量

要创建一个sObject,你需要声明一个变量并将其分配给一个sObject实例。变量的数据类型是sObject类型。

以下示例创建一个类型为Account的sObject变量,并将其分配给名称为Acme的新客户。

Account acct = new Account(Name='Acme');

sObject和字段名称

sObjects的名称对应于相应的标准或自定义对象的API名称。同样,sObject字段的名称对应于相应字段的API名称。

对象和字段的API名称可能与标签不同。例如,“雇员”字段具有“雇员”标签,并在“客户记录”页面上显示为“雇员”,但其API名称为“NumberOfEmployees”。要在Apex中访问此字段,您需要使用该字段的API名称:NumberOfEmployees。

以下是用于自定义对象和自定义字段的API名称的一些规则的亮点。

对于自定义对象和自定义字段,API名称始终以__c后缀结尾。对于自定义关系字段,API名称以__r后缀结尾。例如:

  • 具有Merchandise 标签的自定义对象具有Merchandise__c的API名称。
  • 描述标签的自定义字段的API名称为Description__c。
  • 具有Items标签的自定义关系字段具有Items__r的API名称。

另外,标签中的空格在API名称中用下划线代替。例如,Employee Seniority的自定义字段名称具有Employee_Seniority__c的API名称。

查找对象和字段名称

要查找在Apex中使用的标准对象及其字段的名称,请参阅Salesforce和Force.com的对象参考。

对于自定义对象,请在您的组织中查找对象和字段API名称。从“设置”中,在“快速查找”框中输入对象,然后选择“对象”,然后单击对象的名称。

创建sObjects和添加字段

在插入Salesforce记录之前,必须首先在内存中创建它作为sObject。与其他任何对象一样,sObject使用新的运算符创建:

Account acct = new Account();

API对象名称成为Apex中sObject变量的数据类型。在这个例子中,Account是acct变量的数据类型。

由acct变量引用的客户是空的,因为我们还没有填充它的任何字段。有两种方法可以添加字段:通过构造函数或使用点符号。

添加字段的最快方法是在构造函数中将它们指定为名称 – 值对。例如,这个语句创建一个新的客户sObject,并用字符串值填充它的Name字段。

Account acct = new Account(Name='Acme');
名称字段是客户唯一必填字段,这意味着必须在插入新记录之前填充该字段。但是,您也可以填充其他字段以及新记录。这个例子还增加了一个电话号码和雇员的数量。
Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100);
或者,您可以使用点符号将字段添加到sObject。以下示例与前面的示例相同,但需要多行代码。
Account acct = new Account();
acct.Name = 'Acme';
acct.Phone = '(415)555-1212';
acct.NumberOfEmployees = 100;

使用通用的sObject数据类型

通常,在使用sObjects时,使用特定的sObject数据类型(例如标准对象的Account或Book__c)作为名为Book的自定义对象。但是,当您不知道您的方法正在处理的sObject类型时,可以使用通用的sObject数据类型。
使用通用sObject数据类型声明的变量可以引用任何Salesforce记录,无论它是标准还是自定义对象记录。
A generic sObject variable can point to any Salesforce record

此示例显示如何将通用的sObject变量分配给任何Salesforce对象:客户和名为Book__c的自定义对象。

sObject sobj1 = new Account(Name='Trailhead');
sObject sobj2 = new Book__c(Name='Workbook 1');
相比之下,使用特定的sObject数据类型声明的变量只能引用相同类型的Salesforce记录。
A specific sObject variable can point to the Salesforce record of the same type only

将泛型对象转换为特定的对象类型

当你处理通用的sObjects时,你有时需要将你的sObject变量转换为特定的sObject类型。这样做的好处之一就是能够使用点符号访问字段,这在通用的sObject上是不可用的。由于sObject是所有特定sObject类型的父类型,因此可以将通用sObject转换为特定的sObject。这个例子展示了如何将一个通用的sObject转换为Account。

// 将一个通用的sObject转换为一个Account
Account acct = (Account)myGenericSObject;
// 现在,您可以使用点符号来访问客户上的字段Account
String name = acct.Name;
String phone = acct.Phone;

告诉我更多…

与特定的sObjects类型不同,通用sObjects只能通过newSObject()方法创建。而且,只能通过put()和get()方法访问通用sObject的字段。

在这个单元中,你已经学习了什么sObjects以及如何使用它们。但是,创建一个sObject并不会将其作为记录保存在数据库中。要将sObject保存为记录,并用它来做其他事情,请使用数据操作语言(DML)。要检索记录,请使用Salesforce对象查询语言(SOQL)。查看以后的单元,了解DML和SOQL。

Salesforce Apex (基础1)入门

学习目标

完成本单元后,您将能够:

  • 描述Apex编程语言的主要特点。
  • 保存Apex类并使用Anonymous.Apex调用方法。
  • 使用开发者控制台检查调试日志。

Apex入门

Apex是一种使用类似Java的语法的编程语言,像数据库存储过程一样行事。 Apex使开发人员能够将业务逻辑添加到系统事件中,如按钮点击,相关记录更新和Visualforce页面。

作为一种语言,Apex是:

  • Hosted-Apex在服务器(Force.com平台)上保存,编译并执行。
  • 面向对象-Apex支持类,接口和继承。
  • 强类型-Apex在编译时验证对对象的引用。
  • 多租户感知 – 因为Apex运行在多租户平台上,所以它通过强制实施限制防止代码独占共享资源,从而防止代码走失。
  • 与数据库集成 – 访问和操作记录非常简单。 Apex提供对记录及其字段的直接访问,并提供语句和查询语言来操作这些记录。
  • 关注数据–Apex提供对数据库的事务访问,允许您回滚操作。
  • 易于使用-Apex基于熟悉的Java成语。
  • 易于测试 – Apex为单元测试创​​建,执行和代码覆盖提供了内置的支持。
  • Salesforce确保所有定制的Apex代码在任何平台升级之前执行所有单元测试,并按预期工作。
  • Versioned-Custom Apex代码可以针对不同版本的API进行保存。
Apex is a cloud-based programming language

Apex 语言亮点

像其他面向对象的编程语言一样,这些是Apex支持的一些语言结构:

  • 类,接口,属性和集合(包括数组)。
  • 对象和数组表示法。
  • 表达式,变量和常量。
  • 条件语句(if-then-else)和控制流语句(for循环和while循环)。

与其他面向对象的编程语言不同,Apex支持:

  • 作为Apex的云开发是在云中存储,编译和执行的。
  • 触发器,类似于数据库系统中的触发器。
  • 数据库语句,允许您直接进行数据库调用和查询语言来查询和搜索数据。
  • 事务和回滚。
  • 全局访问修饰符,它比public修饰符更宽松,并允许跨命名空间和应用程序访问。
  • 自定义代码的版本。

另外,Apex是一个不区分大小写的语言。

开发工具

您可以使用Salesforce用户界面直接在浏览器中编写Apex并访问调试信息。打开您的名字下的开发者控制台或快速访问菜单(设置齿轮图标)。

您也可以使用Eclipse的Force.com IDE插件在客户端上编写Apex。请参阅Salesforce Developer网站上的Force.com IDE。

数据类型概述

Apex支持各种数据类型,包括特定于Salesforce的数据类型(sObject数据类型)。
Apex支持以下数据类型。

  • 诸如Integer,Double,Long,Date,Datetime,String,ID,Boolean等原语。
  • 一个sObject,或者作为一个通用的sObject或者一个特定的sObject,比如一个Account,Contact或者MyCustomObject__c(你将在后面的单元中学到更多关于sObjects的知识)。
  • 收藏品包括:
    • 基元,sObjects,用户定义的对象,从Apex类创建的对象或集合的列表(或数组)
    • 一组原语
    • 从基元到基元,sObject或集合的映射
  • 一个键入的值列表,也被称为枚举
  • 用户定义的Apex类
  • 系统提供的Apex类

Apex收藏:列表

列表包含对象的有序集合。 Apex中的列表与数组同义,两者可以互换使用。

以下两个声明是等价的。 colors变量是使用List语法声明的。

List<String> colors = new List<String>();
另外,颜色变量可以声明为一个数组,但分配给一个列表而不是一个数组。
String[] colors = new List<String>();

通常,创建列表而不是数组会更容易,因为列表不需要提前确定需要分配多少个元素。

您可以在创建列表时将元素添加到列表中,或者通过调用add()方法创建列表后添加元素。这第一个例子显示了向元素添加元素的两种方法。

// 创建一个列表并向其中添加元素
List<String> colors = new List<String> { 'red', 'green', 'blue' };

// 创建元素后,将元素添加到列表中
List<String> moreColors = new List<String>();
moreColors.add('orange');
moreColors.add('purple');
列表元素可以通过指定方括号之间的索引来读取,就像数组元素一样。另外,您可以使用get()方法读取列表元素。这个例子是基于前面例子中创建的列表,并展示了如何使用任一方法读取列表元素。该示例还显示了如何迭代数组元素。
// 从列表中获取元素
String color1 = moreColors.get(0);
String color2 = moreColors[0];
System.assertEquals(color1, color2);

// 遍历列表来读取元素
for(Integer i=0;i<colors.size();i++) {
    // Write value to the debug log
    System.debug(colors[i]);
}

超越基础

Apex支持另外两种集合类型:Set和Map。您可以在“Apex开发人员指南”的“集合”部分了解更多。

Apex 类

Apex类的好处之一是代码重用。类方法可以被触发器和其他类调用。以下教程将引导您保存组织中的示例类,使用此类发送电子邮件以及检查调试日志。

保存一个Apex类

将EmailManager类保存在您的组织中:

  1. 打开您的名字下的开发者控制台或快速访问菜单(设置齿轮图标Setup gear icon)。
  2. 在开发者控制台中,点击 File | New | Apex Class ,然后输入EmailManager作为类名称,然后单击OK。
  3. 用EmailManager类的例子替换默认的类体。

    EmailManager类具有公共方法(sendMail()),用于发送电子邮件并使用Apex类库的内置消息处理方法。另外,这个类有一个私有的帮助方法(inspectResults()),它不能被外部调用,因为它是私有的,但只在类中使用。这个帮助方法检查电子邮件发送调用的结果,并由sendMail()调用。

    public class EmailManager {
    
        // 公共方法
        public void sendMail(String address, String subject, String body) {
            // 创建一个电子邮件对象
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {address};
            mail.setToAddresses(toAddresses);
            mail.setSubject(subject);
            mail.setPlainTextBody(body);
            // 将此电子邮件传递给内置的sendEmail方法
            // 消息类的
            Messaging.SendEmailResult[] results = Messaging.sendEmail(
                                     new Messaging.SingleEmailMessage[] { mail });
            
            // 调用一个帮助器方法来检查返回的结果
            inspectResults(results);
        }
        
        // 辅助方法
        private static Boolean inspectResults(Messaging.SendEmailResult[] results) {
            Boolean sendResult = true;
            
            // sendEmail返回一个结果对象数组。
            // 遍历列表来检查结果。
            // 在这个类中,方法只发送一封邮件,
            // 所以我们应该只有一个结果。
            for (Messaging.SendEmailResult res : results) {
                if (res.isSuccess()) {
                    System.debug('Email sent successfully');
                }
                else {
                    sendResult = false;
                    System.debug('发生以下错误:' + res.getErrors());                 
                }
            }
            
            return sendResult;
        }
    
    }
  4. 点击Ctrl + S保存你的课程。

    超越基础

    刚刚保存的类使用面向对象编程(OOP)。该类封装了与管理电子邮件有关的方法。为了成为OOP的一个完美的例子,类还将包含成员变量(属性)和访问器方法来访问这些属性,但为了简单起见,我们的类没有这些。

    Salesforce在您保存时编译您的类。

调用方法发送电子邮件

我们来调用公共方法。我们将使用匿名的Apex执行来执行此操作。匿名Apex允许您即时运行代码行,并且是调用Apex的一种便捷方式,尤其是测试功能。与任何其他Apex执行一样,也会生成调试日志结果。

注意

还有其他方法可以通过触发器来调用Apex。您将在另一个模块中了解有关触发器的更多信息。

  1. 在开发者控制台中,点击 Debug | Open Execute Anonymous Window.
  2. 在打开的窗口中,输入以下内容。用您的电子邮件地址替换“您的电子邮件地址”。
    EmailManager em = new EmailManager();
    em.sendMail('Your email address', 'Trailhead Tutorial', '123 body');
    
  3. 点击 Execute.

    现在这个方法已经执行了,你应该在收件箱里收到一封电子邮件。查看你的电子邮件!

检查调试日志

调试日志对于调试代码很有用。当Apex方法执行时,调用会记录在调试日志中。而且,您可以将自己的调试消息写入日志,这有助于在出现错误时调试代码。由sendMail()调用的inspectResults()辅助方法使用System.debug()方法将消息写入日志,以指示电子邮件发送操作是成功还是有错误。您可以在执行该方法时生成的调试日志中查找这些消息。

  1. 在开发者控制台中,点击日志标签,然后双击列表中最近的日志。
  2. 选择 Debug Only 来过滤日志,以便仅显示System.debug()语句的日志行。
    Filter the debug log in the Developer Console to view debug messages

    注意

    另外,您可以通过在“筛选”字段中搜索任何关键字来筛选调试日志,或者通过选择任何其他选项来筛选调试日志。有关更多信息,请参阅日志检查器帮助。

    在过滤的日志视图中,您会看到以下消息,假设电子邮件发送没有错误。

    DEBUG|Email sent successfully

调用静态方法

因为我们类中的sendMail()方法不能访问类成员变量,所以它不需要是一个实例方法。让我们通过添加静态关键字到它的声明来将其更改为静态方法。静态方法比实例方法更容易调用,因为它们不需要在类的实例上调用,而是直接在类名上调用。

  1. 在开发人员控制台中,找到EmailManager类的打开选项卡,并将sendMail()方法定义的第一行修改为以下内容(唯一的变化是添加的static关键字)。
    public static void sendMail(String address, String subject, String body) {
  2. 按Ctrl + S保存该类。
  3. 修改“执行匿名”窗口中的语句以调用类名称上的静态方法。
    EmailManager.sendMail('Your email address', 'Trailhead Tutorial', '123 body');
    
  4. 点击 Execute.

    现在这个方法已经执行了,你可以像在前面的步骤一样检查你的电子邮件,以及可选的调试日志。

Salesforce Visualforce (基础9)自定义控制器

学习目标

完成本单元后,您将能够:

  • 解释一下自定义控制器是什么,并描述其关键属性。
  • 创建一个自定义控制器类。
  • 在Visualforce页面上使用自定义控制器。

自定义控制器介绍

自定义控制器包含可由Visualforce页面使用的自定义逻辑和数据操作。例如,自定义控制器可以检索要显示的项目列表,向外部Web服务发出调用,验证和插入数据等等,并且所有这些操作都将可用于Visualforce页面,该页面将其用作控制器。
在其他地方介绍了Visualforce如何支持用于构建Web应用程序的模型 – 视图 – 控制器(MVC)设计模式。控制器通常检索要在Visualforce页面中显示的数据,并包含响应页面操作而执行的代码(如正在单击的按钮)。当您使用标准控制器时,平台为您提供了大量的标准功能。

但是一个尺寸并不适合所有的,并不是所有的网络应用程序都是“标准的”。当你想覆盖现有的功能,通过应用程序自定义导航,使用标注或Web服务,或者如果你需要更好地控制信息的访问方式对于您的页面,Visualforce可让您占据统治地位。您可以使用Apex编写自定义控制器,并从头到尾完全控制您的应用逻辑。

创建一个使用自定义控制器的Visualforce页面

通过在<apex:page>控制器属性中引用控制器类的名称,将自定义控制器添加到Visualforce页面。
当您的页面使用自定义控制器时,不能使用标准控制器。页面使用不同的属性来设置自定义控制器。
  1. 打开开发者控制台,然后点击 File | New | Visualforce Page 创建一个新的Visualforce页面。输入ContactsListController作为页面名称。
  2. 在编辑器中,用以下替换标记。
    <apex:page controller="ContactsListController">
        <apex:form>
            <apex:pageBlock title="Contacts List" id="contacts_list">
                
                <!-- Contacts List goes here -->
    
            </apex:pageBlock>
        </apex:form>
    </apex:page>
    当你试图保存这个页面时,你会得到一个错误,因为ContactsListController还不存在。不用担心,接下来我们会解决这个问题。

创建一个自定义控制器Apex类

自定义控制器只是一个Apex类,您可以使用Developer Console编写自己的类。
有许多系统和实用程序类可以帮助您编写自定义控制器逻辑,但将类用作自定义控制器的唯一要求是它存在。
  1. 打开开发者控制台,然后点击 File | New | Apex Class 创建一个新的Apex类。输入ContactsListController作为类名。
  2. 在编辑器中,用以下代码替换任何代码。
    public class ContactsListController {
    
        // Controller code goes here
    
    }
    与Visualforce页面一样,当您更改时,您需要保存对Apex的更改。
    这并不多,而且还没有做任何事情,但它确实使错误消失在Visualforce页面上。所以…
  3. 切换回Visualforce页面并再次保存。
    错误信息应该消失,页面成功保存。
  4. 点击预览打开您的页面的预览,您可以在进行更改时查看。
    应该打开一个新窗口,显示标准的Salesforce页面标题和侧边栏元素,但没有内容。

乍一看,你创建的这两个新项目看起来并不是很有趣。但是即使它们是90%的占位符代码,Visualforce页面和Apex控制器这两个项目也是相互关联的。只要你添加更多的代码到控制器,你的页面就可以使用它。

超越基础

您可能已经注意到,这个自定义控制器类不会从另一个类继承,也不会实现一个有希望符合Visualforce控制器要求的接口。即使是复杂的控制器也不会做这些事情,因为没有任何这样的类继承或接口实现。这使您可以自由地创建自己的类和接口,随着您对Apex的经验的增加。

添加一个方法来检索记录

创建一个运行SOQL查询的getter方法,该查询返回要在页面上显示的记录。
大多数控制器的主要目的是检索显示数据或处理数据更新。在这个简单的控制器中,您只需运行一个基本的SOQL查询来查找联系人记录,然后使这些记录可用于Visualforce页面。
  1. 在ContactsListController类中,用下面的代码替换// Controller代码在这里注释行。
    private String sortOrder = 'LastName';
        
    public List<Contact> getContacts() {
        
        List<Contact> results = Database.query(
            'SELECT Id, FirstName, LastName, Title, Email ' +
            'FROM Contact ' +
            'ORDER BY ' + sortOrder + ' ASC ' +
            'LIMIT 10'
        );
        return results;
    }
    此代码添加一个私有成员变量,一个名为sortOrder的字符串和一个公共方法getContacts()。 sortOrder很容易理解,它只是通过排序联系人的字段的名称。
    getContacts()也是相当简单的,但是如果你以前没有见过Apex,可能起初很难解析。该方法的作用是执行SOQL查询以获取联系人记录列表,然后将该联系人列表返回给方法调用者。来电者是谁?当然是Visualforce页面!
  2. 在ContactsListWithController页面中,将<! – 联系人列表转到这里 – >注释行替换为以下标记。
    <!-- Contacts List -->
    <apex:pageBlockTable value="{! contacts }" var="ct">
    
        <apex:column value="{! ct.FirstName }"/>
        <apex:column value="{! ct.LastName }"/>
        <apex:column value="{! ct.Title }"/>
        <apex:column value="{! ct.Email }"/>
        
    </apex:pageBlockTable>
    当您保存此页面时,您应该看到一张熟悉的联系信息表格。
    A contacts list backed by a custom controller

ContactsListWithController页面的标记应该看起来相当熟悉。除了<apex:page>标签的控制器属性之外,它与用标准控制器创建页面的代码大致相同。

不同的是当{!联系人}表达式进行评估。在此页面上,Visualforce将该表达式转换为对控制器的getContacts()方法的调用。该方法返回联系人记录列表,这正是<apex:pageBlockTable>所期望的。

getContacts()方法被称为getter方法,它是一个通用模式,其中{!您的Visualforce标记中的someExpression}将自动连接到控制器中名为getSomeExpression()的方法。这是让页面访问需要显示的数据的最简单的方法。

添加一个新的操作方法

在自定义控制器中创建操作方法以响应用户在页面上的输入。
显示数据很棒,但是对用户操作的回应对于任何网络应用程序都是至关重要的使用自定义控制器,您可以通过编写操作方法来响应用户活动,从而创建尽可能多的自定义操作,以支持页面。
  1. 在ContactsListController类中,getContacts()方法的下面添加以下两个方法。
    public void sortByLastName() {
        this.sortOrder = 'LastName';
    }
        
    public void sortByFirstName() {
        this.sortOrder = 'FirstName';
    }
    这两个方法改变了sortOrder私有变量的值。在检索联系人的SOQL查询中使用sortOrder,并且更改sortOrder将更改结果的顺序。
  2. 在ContactsListWithController页面中,使用以下标记替换ct.FirstName和ct.LastName的两个<apex:column>标记。
    <apex:column value="{! ct.FirstName }">
        <apex:facet name="header">
            <apex:commandLink action="{! sortByFirstName }" 
                reRender="contacts_list">First Name
            </apex:commandLink>
        </apex:facet>
    </apex:column>
    
    <apex:column value="{! ct.LastName }">
        <apex:facet name="header">
            <apex:commandLink action="{! sortByLastName }" 
                reRender="contacts_list">Last Name
            </apex:commandLink>
        </apex:facet>
    </apex:column>

    尽管可视外观保持不变,但如果现在单击“名字和姓氏”列标题,它们将更改联系人列表的排序顺序。太好了!
    新的标记将两个嵌套组件添加到每个<apex:column>组件。 <apex:column>本身有一个纯文本标题,但是我们希望使标题可点击。 <apex:facet>让我们将列标题的内容设置为我们想要的任何内容。而我们想要的是一个调用正确的操作方法的链接。该链接是使用<apex:commandLink>组件创建的,action属性设置为引用控制器中action方法的表达式。 (请注意,与getter方法相比,action方法的名称与引用它们的表达式相同。)

    点击链接时,会触发控制器中的操作方法。该操作方法更改排序顺序专用变量,然后该表被重新渲染。当表格被重新渲染时,{!联系人}被重新评估,重新查询以任何排序顺序刚刚设置。最终的结果是按照用户点击所请求的顺序来使用表格。

超越基础

名字和姓氏列的标题文本在这个标记中被硬编码。但是,如果你的用户不全都使用英文呢?标准Salesforce用户界面已翻译所有标准对象的字段名称版本,并且可以为自定义对象提供自己的翻译。你将如何访问这些?试试这个标记,而不是纯文本:<apex:outputText value =“{!$ ObjectType.Contact.Fields.FirstName.Label}”/>。即使您的组织使用相同的语言,这也是引用字段标签的正确方法,因为如果字段名称被更改,它将自动更新。

告诉我更多…

自定义控制器和Apex语言让您可以在Visualforce页面中完成任何您能想到的任何事情。
Getter方法将数据从您的控制器中拖出到您的页面上。有相应的setter方法可以让你从页面提交值到你的控制器。就像getter方法一样,你在setter前面加上“set”,除此之外,它们只是一个方法。

getter和setter的替代方法是使用Apex属性。属性是一种变量与getter和setter方法的组合,它们的语法可以更清楚地将它们组合在一起。引用自定义对象的简单属性可能会这样声明。

public MyObject__c myVariable { get; set; }

属性可以是公共的或私有的,可以是只读的,甚至可以只写,通过省略get或set。你可以为get或set方法创建实现,当你想要执行额外的逻辑,除了简单地保存和检索一个值。

属性是Apex的一般功能,不是Visualfor特有的。 Apex是一种完整的编程语言,除了成为构建复杂的Visualforce页面的天然合作伙伴外,它还被用于许多其他的Force.com开发环境。请参阅此处其他位置的Apex主题以及本页末尾的资源,了解如何学习充分利用Apex。

Visualforce请求和响应的生命周期最初可能看起来很复杂。尤其重要的是要明白,没有特定的getter或setter(或属性,如果你使用它们)的顺序被调用,所以你不能在它们之间引入顺序执行的依赖关系。 Visualforce开发人员指南的相关章节中提供了更多详细信息,特别是“自定义控制器和控制器扩展”一章。

Salesforce Visualforce (基础8)静态资源

学习目标

完成本单元后,您将能够:

  • 解释什么样的静态资源以及为什么要使用它们。
  • 解释单个和压缩静态资源之间的差异。
  • 创建并上传一个静态资源。
  • 将静态资源添加到Visualforce页面。

静态资源简介

静态资源允许您上传可在Visualforce页面中引用的内容。资源可以是档案(如.zip和.jar文件),图像,样式表,JavaScript和其他文件。
静态资源由Force.com管理和分发,Force.com充当文件的内容分发网络(CDN)。缓存和分发是自动处理的。

静态资源使用$ Resource全局变量进行引用,该变量可以由Visualforce直接使用,也可以用作诸如URLFOR()之类的函数的参数。

创建并上传一个简单的静态资源

为独立的静态资产创建一个简单的,独立的静态资源。
当您的静态资产与其他资产无关时(即不是一组类似资产(如一组图标)的一部分),创建独立静态资源是最容易的。

  1. 下载jQuery JavaScript库的当前版本。在写这个是jQuery 3.1.0。
    如果链接不起作用,或者您想确保您正在下载最新版本,请访问http://jquery.com/download/并下载最新版本。

    注意

    右键单击直接链接下载文件,而不是在浏览器窗口中打开它。

  2. 在安装程序中,在快速查找框中输入静态资源,然后选择静态资源,然后单击新建。
  3. 输入名称的jQuery。
  4. 点击选择文件,然后选择您以前下载的jQuery JavaScript文件。
    Creating a simple static resource
  5. 如果看到缓存控制菜单,请选择公共。
    此项目在所有组织中都不可见。
  6. 点击 Save.

你现在有一个jQuery的静态资源版本,你可以在你的Visualforce页面中使用,通过在表达式中引用它,比如{! $ Resource.jQuery}。真正好的是,您可以编辑静态资源并将其更新到jQuery 3.1.2,而无需更改任何Visualforce页面。静态资源引用处理细节。

将一个静态资源添加到Visualforce页面

使用$ Resource全局变量和点符号来引用一个独立的静态资源。
  1. 打开开发者控制台,然后点击 File | New | Visualforce Page 创建一个新的Visualforce页面。输入HelloJQuery作为页面名称。
  2. 在编辑器中,用以下替换标记。
    <apex:page>
        
        <!-- Add the static resource to page's <head> -->
        <apex:includeScript value="{! $Resource.jQuery }"/>
        
        <!-- A short bit of jQuery to test it's there -->
        <script type="text/javascript">
            jQuery.noConflict();
            jQuery(document).ready(function() {
                jQuery("#message").html("Hello from jQuery!");
            });
        </script>
        
        <!-- Where the jQuery message will appear -->
        <h1 id="message"></h1>
        
    </apex:page>
  3. 点击预览打开您的页面的预览,您可以在进行更改时查看。
    应打开一个新窗口,显示标准的Salesforce页面标题和侧边栏元素,以及jQuery的简短消息。
    除了说明如何在您的Visualforce页面上包含JavaScript资源之外,此页面没有太多工作。关键是使用$ Resource全局变量。使用点符号将它与<apex:includeScript>(用于JavaScript文件),<apex:stylesheet>(用于CSS样式表)或<apex:image>(用于图形文件)标签中的资源名称相结合它到你的页面。

创建和上传压缩的静态资源

创建压缩的静态资源,将通常一起更新的相关文件分组在一起。
当您的静态资产是一组相关项目(例如,一组应用程序图标或复杂的JavaScript库)时,最好创建一个压缩的静态资源。创建一个包含所有要组合在一起的元素的压缩文件,并仅将一个压缩文件上传到Force.com。

  1. 以ZIP格式下载当前版本的jQuery Mobile JavaScript库。在写这个是jQuery Mobile 1.4.5
    如果链接不起作用,或者您想确保您正在下载最新版本,请访问http://jquerymobile.com/download/并下载最新版本的ZIP文件。
  2. 在安装程序中,在快速查找框中输入静态资源,然后选择静态资源,然后单击新建。
  3. 输入名称的jQueryMobile。
  4. 点击选择文件,然后选择您以前下载的jQuery Mobile ZIP文件。
    Creating a zipped static resource
  5. 如果看到缓存控制菜单,请选择公共。
    此项目在所有组织中都不可见。
  6. 点击 Save.

    注意

    如果jQuery Mobile ZIP文件大于5 MB,则编辑压缩文件以除去/ demo /目录及其内容,并上传较小的zip压缩文件。

现在,您可以在Visualforce页面中使用jQuery Mobile的静态资源版本。您将学习如何在压缩的静态资源中引用单个文件。

将压缩的静态资源添加到Visualforce页面

使用$ Resource全局变量和URLFOR()函数来引用压缩静态资源中的项目。
URLFOR()函数可以将对压缩的静态资源的引用以及其中的项目的相对路径组合起来,以创建可用于引用静态资产的Visualforce组件的URL。例如,URLFOR($ Resource.jQueryMobile,’images / icons-png / cloud-black.png’)将URL返回到压缩静态资源中的特定图形资源,可以由<apex:image>组件使用。您可以为<apex:includeScript>和<apex:stylesheet>组件构建JavaScript和样式表文件的类似URL。
  1. 打开开发者控制台,然后点击 File | New | Visualforce Page 创建一个新的Visualforce页面。
  2. 在编辑器中,用以下替换标记。
    <apex:page showHeader="false" sidebar="false" standardStylesheets="false">
        
        <!-- Add static resources to page's <head> -->
        <apex:stylesheet value="{!
              URLFOR($Resource.jQueryMobile,'jquery.mobile-1.4.5/jquery.mobile-1.4.5.css')}"/>
        <apex:includeScript value="{! $Resource.jQuery }"/>
        <apex:includeScript value="{!
             URLFOR($Resource.jQueryMobile,'jquery.mobile-1.4.5/jquery.mobile-1.4.5.js')}"/>
    
        <div style="margin-left: auto; margin-right: auto; width: 50%">
            <!-- Display images directly referenced in a static resource -->
            <h3>Images</h3>
            <p>A hidden message:
                <apex:image alt="eye" title="eye"
                     url="{!URLFOR($Resource.jQueryMobile, 'jquery.mobile-1.4.5/images/icons-png/eye-black.png')}"/>
                <apex:image alt="heart" title="heart"
                     url="{!URLFOR($Resource.jQueryMobile, 'jquery.mobile-1.4.5/images/icons-png/heart-black.png')}"/>
                <apex:image alt="cloud" title="cloud"
                     url="{!URLFOR($Resource.jQueryMobile, 'jquery.mobile-1.4.5/images/icons-png/cloud-black.png')}"/>
            </p>
    
        <!-- Display images referenced by CSS styles,
             all from a static resource. -->
        <h3>Background Images on Buttons</h3>
        <button class="ui-btn ui-shadow ui-corner-all
             ui-btn-icon-left ui-icon-action">action</button>
        <button class="ui-btn ui-shadow ui-corner-all
             ui-btn-icon-left ui-icon-star">star</button>
        </div>
    </apex:page>
  3. 点击预览打开您的页面的预览,您可以在进行更改时查看。
    打开一个新窗口,并显示来自jQuery Mobile静态资源的一些图像。

    jQuery buttons via static resources

为简单起见,此页面仅显示如何在Visualforce页面上引用压缩的静态资源。例如,如果您想了解更多关于如何结合Visualforce和JavaScript库(如jQuery Mobile)的更多资源,请参阅附加资源。

告诉我更多…

静态资源通常是将设计和脚本资产包含在Force.com应用程序中的最佳方式,并且可以通过多种方式将这些资源与Visualforce页面结合使用。
如果您喜欢为jQuery和jQuery Mobile组合一些静态资源,您将会喜欢我们预先打包的静态资源集合。 Salesforce Mobile Packs允许您使用行业标准网络技术(如HTML5,CSS3和JavaScript)以及流行的框架(如jQuery Mobile,AngularJS和Backbone.js)构建与Salesforce Platform集成的Web和混合应用程序。

Salesforce Visualforce (基础7)标准列表控制器

学习目标

完成本单元后,您将能够:

  • 解释Visualforce标准列表控制器是什么以及它与标准(记录)控制器有什么不同。
  • 列出标准列表控制器提供的与标准控制器不同的三个操作。
  • 在Visualforce页面上使用标准列表控制器显示记录列表。
  • 定义分页,并能够将其添加到Visualforce页面。

标准列表控制器简介

标准列表控制器允许您创建可以显示或执行一组记录的Visualforce页面。
显示记录列表是几乎所有Web应用程序的基本行为。 Visualforce使得显示相同类型的记录列表非常容易,只需使用标记,不需要后端代码。秘密,像往常一样,是标准的控制器,在这种情况下,标准的列表控制器。

标准列表控制器提供了许多强大的自动行为,例如查询特定对象的记录以及使集合变量中的记录可用,以及通过结果过滤和分页。将标准列表控制器添加到页面与添加标准(记录)控制器非常相似,但意图同时处理多条记录,而不是一次记录一条记录。

显示记录列表

使用标准列表控制器和迭代组件(如<apex:pageBlockTable>)来显示记录列表。
标准(记录)控制器可以轻松地将单个记录加载到您可以在Visualforce页面上使用的变量中。标准列表控制器是相似的,除了单个记录之外,它将记录的列表或集合加载到变量中。

因为你正在处理一个集合,而不是一个单独的记录,你需要使用一个迭代组件来显示它们。一个迭代组件与一组相似的项目一起工作,而不是一个单一的值。一个迭代组件循环遍历其集合,并且对于每个记录,基于作为组件标记的一部分提供的模板生成一些输出。这听起来很复杂,但是当你阅读标记时你会很快理解它。

使用标准列表控制器的标记与使用标准的一次一个记录控制器的标记几乎相同。为了显而易见,以下示例中以粗体突出显示了主要区别。

  1. 打开开发者控制台,然后点击 File | New | Visualforce Page 创建一个新的Visualforce页面。输入ContactList作为页面名称。
  2. 在编辑器中,用以下替换标记。
    <apex:page standardController="Contact" recordSetVar="contacts">
        <apex:pageBlock title="Contacts List">
            
            <!-- Contacts List -->
            <apex:pageBlockTable value="{! contacts }" var="ct">
                <apex:column value="{! ct.FirstName }"/>
                <apex:column value="{! ct.LastName }"/>
                <apex:column value="{! ct.Email }"/>
                <apex:column value="{! ct.Account.Name }"/>
            </apex:pageBlockTable>
            
        </apex:pageBlock>
    </apex:page>
  3. 点击预览打开您的页面的预览,您可以在进行更改时查看。
    应打开一个新窗口,显示标准Salesforce页面标题和侧栏元素以及联系人列表。

    A list of contacts from the standard list controller

使用标准列表控制器与使用标准控制器非常相似。首先在<apex:page>组件上设置standardController属性,然后在同一个组件上设置recordSetVar属性。标准控制器属性设置对象使用,就像标准控制器一样。 recordSetVar设置要创建的变量的名称与记录的集合,在这里{!联系人}。按照惯例,这个变量通常被命名为对象名称的复数。

<apex:pageBlockTable>是一个迭代组件,用于生成一个数据表,并附有平台样式。这是表格标记中发生的事情。

  • 将<apex:pageBlockTable>的值属性设置为由标准列表控制器加载的变量{!联系人}。这是<apex:pageBlockTable>将使用的记录列表。
  • 对于该列表中的每个记录,一次一条记录<apex:pageBlockTable>将该记录分配给<apex:pageBlockTable>的var属性中指定的变量。在这种情况下,该变量被命名为ct。
  • 对于每个记录,<apex:pageBlockTable>使用由<apex:pageBlockTable>主体内的<apex:column>组合集定义的行在表中构造一个新行。 <apex:column>组件依次使用表示当前记录的ct变量来提取该记录的字段值。
  • 在循环之外,<apex:pageBlockTable>使用<apex:column>组件中的字段通过查找每个字段的标签来创建列标题。

这是非常重要的,迭代组件是第一次很难理解。你现在可以做的最好的事情是尝试创建你自己的。选择你想在表格中显示的字段。查找<apex:pageBlockTable>和<apex:column>的不同属性,并进行试验,直到您感觉舒适。您也可以尝试使用其他一些迭代组件,例如<apex:dataList>甚至是<apex:repeat>。

添加列表视图过滤到列表

使用 {! listViewOptions}来获取可用于对象的列表视图过滤器的列表。使用 {! filterId}设置列表视图过滤器用于标准列表控制器的结果。
标准列表控制器提供了许多可用于更改列表显示的功能。其中最强大的是列表视图过滤器。您可以声明式地创建列表视图过滤器,使用点击代替代码,标准列表控制器允许您使用页面上任何已定义的列表视图过滤器。

  1. 将整个<apex:pageBlock>包装在<apex:form>标记中。
    要更改标准列表控制器的列表视图过滤器,您需要将新值提交回服务器。执行此提交的标准方法是使用使用<apex:form>组件创建的表单。
  2. 在<apex:pageBlock>标签中添加以下属性。
    id="contacts_list"
    这给了<apex:pageBlock>一个“名字”,我们可以使用它来获得一个很酷的Ajax效果,我们稍后会解释一下。
  3. 打开<apex:pageBlock>标记后,在<apex:pageBlockTable>上方,添加以下标记。
    Filter: 
    <apex:selectList value="{! filterId }" size="1">
        <apex:selectOptions value="{! listViewOptions }"/>
        <apex:actionSupport event="onchange" reRender="contacts_list"/>
    </apex:selectList>
    您的页面的完整代码应该是这样的。
    <apex:page standardController="Contact" recordSetVar="contacts">
        <apex:form>
    
            <apex:pageBlock title="Contacts List" id="contacts_list">
            
                Filter: 
                <apex:selectList value="{! filterId }" size="1">
                    <apex:selectOptions value="{! listViewOptions }"/>
                    <apex:actionSupport event="onchange" reRender="contacts_list"/>
                </apex:selectList>
    
                <!-- Contacts List -->
                <apex:pageBlockTable value="{! contacts }" var="ct">
                    <apex:column value="{! ct.FirstName }"/>
                    <apex:column value="{! ct.LastName }"/>
                    <apex:column value="{! ct.Email }"/>
                    <apex:column value="{! ct.Account.Name }"/>
                </apex:pageBlockTable>
                
            </apex:pageBlock>
    
        </apex:form>
    </apex:page>
    一个新的过滤器控件出现在列表上方。
    Contacts list with list views filter
  4. 从菜单中选择一个不同的过滤器。联系人列表发生了什么?

从刚刚创建的“滤镜”菜单中进行新选择时,应该注意两点。首先,当您选择一个新的过滤器时,记录列表会发生变化。 (您可能需要尝试几个不同的选项,在标准DE组织中使用示例数据时,某些列表视图将显示相同的记录。)

其次,更微妙的是,该列表正在更新,无需重新加载整个页面。这个“Ajax”效果是由<apex:actionSupport>组件中的reRender =“contacts_list”属性提供的。组件和reRender的联合作用是仅更新reRender属性中指定的页面部分。由于您已将id =“contacts_list”添加到<apex:pageBlock>,因此操作完成后,只更新<apex:pageBlock>,而不重新加载整个页面。

此页面上新功能的完整生命周期就像这样。

  • 当页面加载时,<apex:selectList>通过从{!中获取列表来构建可用过滤器的菜单。 listViewOptions}表达式。 listViewOptions是由标准列表控制器提供的属性。
  • 当您从菜单中选择新选项时,onchange:event将由<apex:actionSupport>组件触发。
  • 当onchange激发时,页面通过将新项目提交给在<apex:selectList>中设置的filterId属性来提交选定的新列表视图。
  • 当属性更新时,页面从服务器获得一个新的响应,在contacts变量中有一个新的匹配记录集合。
  • 但是因为<apex:actionSupport>指定仅渲染页面的一部分,所以页面使用Ajax异步JavaScript更新,而不是整页重新加载。

最终结果是,只需添加几行标记即可获得复杂而复杂的行为。

将分页添加到列表中

使用标准列表控制器的分页功能,允许用户一次查看一个“页面”的长记录列表。
到目前为止,您开发的功能非常好,并且可以很好地处理在Developer Edition组织中作为样本数据提供的记录的简短列表。但是当你拥有一个真正的组织,拥有数百甚至数千甚至数百万条记录时会发生什么?在一个页面上查看它们并不是很好!

实际上,默认情况下,标准列表控制器仅显示符合过滤条件的前20条记录(如果有的话)。你怎么能让人们访问超过前20条记录,或每页更多的记录,而不仅仅是20?

答案是分页。这是一个标准的Web应用程序用户界面元素,它允许您一次使用“下一个”和“上一个”链接一次向前和向后浏览一长串记录,即“页面”。您可以使用标准列表控制器将其添加到您的页面,以及方便程度(如进度指示器和菜单)来更改每页的记录数。

  1. 在联系人列表<apex:pageBlockTable>下方,添加以下标记。
    <!-- Pagination -->
    <table style="width: 100%"><tr>
    
        <td>
            <!-- Page X of Y -->
        </td>            
    
        <td align="center">
            <!-- Previous page -->
            <!-- Next page -->
        </td>
        
        <td align="right">
            <!-- Records per page -->
        </td>
    
    </tr></table>
    
    这将创建一个HTML表格,它将包含要添加的三个分页控件。

    注意

    在真实的网页中,您可能会使用更多的语义标记和单独的样式,但是现在,简单的HTML是简洁明了的。

  2. <!– Page X of Y –>替换为以下标记
    Page: <apex:outputText 
        value=" {!PageNumber} of {! CEILING(ResultSize / PageSize) }"/>
    这会将进度指示器添加到列表中,指示用户正在查看哪个页面,以及有多少人在那里。如果你在DE组织中尝试这个,可能会说“第1页,共1页”。
  3. 用下面的标记替换<! – 上一页 – >和<! – 下一页 – >注释行。
    <!-- Previous page -->
    <!-- active -->
    <apex:commandLink action="{! Previous }" value="« Previous"
         rendered="{! HasPrevious }"/>
    <!-- inactive (no earlier pages) -->
    <apex:outputText style="color: #ccc;" value="« Previous"
         rendered="{! NOT(HasPrevious) }"/>
    
    &nbsp;&nbsp;  
    
    <!-- Next page -->
    <!-- active -->
    <apex:commandLink action="{! Next }" value="Next »"
         rendered="{! HasNext }"/>
    <!-- inactive (no more pages) -->
    <apex:outputText style="color: #ccc;" value="Next »"
         rendered="{! NOT(HasNext) }"/>
    这个标记提供页面上的上一个和下一个链接。标记包含两种可能性:当在给定方向上有更多记录要查看时,则链接处于活动状态;并且当给定方向上没有更多页面时,该链接被禁用。
  4. 用下面的标记替换<! – 记录每页 – >注释行。
    Records per page:
    <apex:selectList value="{! PageSize }" size="1">
        <apex:selectOption itemValue="5" itemLabel="5"/>
        <apex:selectOption itemValue="20" itemLabel="20"/>
        <apex:actionSupport event="onchange" reRender="contacts_list"/>
    </apex:selectList>
    这个标记提供了一个菜单来改变每页记录的数量。这里我们只添加了一个选项,可以在页面上显示更少的记录。从列表中选择“5”,看看列表和其他控件发生了什么。
结果是一个列表页面,具有相当多的标准列表控制器提供的功能。

Contacts list with pagination controls

在进度指示器中,有三个属性用于指示有多少页:PageNumber,ResultSize和PageSize。最后两个实际用于公式表达式,将数字四舍五入到最接近的整数。这可以防止指标说一些无意义的东西,比如“1.6的第2页”。

在分页控件中,<apex:commmandLink>组件引用标准列表控制器Previous和Next提供的两个操作方法。结果是执行“上一个”或“下一个”操作的链接。

但是,这个呈现的属性是什么,有如{! HasPrevious}?这是Visualforce使您能够有条件地显示组件的方式,也就是说,取决于布尔表达式的结果。这里的页面标记是引用由标准列表控制器HasPrevious和HasNext提供的布尔属性,它让你知道在给定的方向上是否有更多的记录。通过在呈现属性中使用表达式,可以在页面上显示或隐藏该组件的结果。这是上次链接在第一次加载页面时变灰的方法,但是如果您通过单击“下一步”链接前进,则“前一个”链接变为活动状态。

每个页面选择菜单的记录使用了前面使用过的<apex:selectList>,但不是调用控制器方法来获取菜单值,而是使用<apex:selectOption>元素来获取所需的值。同样,<apex:actionSupport>标签会导致菜单在所选值更改时触发其操作,并且会再次使用reRender =“contacts_list”更新<apex:pageBlock>。这里的新部分是<apex:selectList>设置标准列表控制器的PageSize属性。

告诉我更多…

标准列表控制器提供了许多在网络应用程序中常见的功能,这里已经涵盖了许多功能。
例如,除了一次往回移动一页的“上一页”和“下一页”操作之外,还有第一个和最后一个操作将转到记录列表的开头或结尾。

在标记的背后,标准列表控制器基于StandardSetController Apex类。您可以在Force.com Apex Code开发人员指南中阅读更多关于它以及它提供的所有功能。

房间里有一头小象,我们在这里创建的例子,它的名字是排序。通常需要为列表设置默认的排序顺序,并且还需要具有排序影响的列标题,以便您即时更改排序顺序。简单的事实是,您无法单独使用Visualforce来影响排序顺序。尽管支持排序和可点击列标题所需的附加Visualforce标记和Apex代码量并不是很大,但它确实需要自定义代码。查看一些起点的附加资源。