像其他面向对象的编程语言一样,这些是Apex支持的一些语言结构:
类,接口,属性和集合(包括数组)。对象和数组表示法。表达式,变量和常量。条件语句(if-then-else)和控制流语句(for循环和while循环)。与其他面向对象的编程语言不同,Apex支持:
作为Apex的云开发是在云中存储,编译和执行的。触发器,类似于数据库系统中的触发器。数据库语句,允许您直接进行数据库调用和查询语言来查询和搜索数据。事务和回滚。全局访问修饰符,它比public修饰符更宽松,并允许跨命名空间和应用程序访问。自定义代码的版本。另外,Apex是一个不区分大小写的语言。
Switch
String waterLevel = 'empty'; //option 1 using a single value switch on waterLevel{ when 'empty'{ System.debug('Fill the tea kettle'); } when 'half'{ System.debug('Fill the tea kettle'); } when 'full'{ System.debug('The tea kettle is full'); } when else{ System.debug('Error!'); } } //option 2 using multiple values switch on waterLevel{ when 'empty', 'half'{ //when waterLevel is either empty or half System.debug('Fill the tea kettle'); } when 'full'{ System.debug('The tea kettle is full'); } when else{ System.debug('Error!'); }输出结果为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4V3CrfjK-1572587509250)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571644156217.png)]
打开开发人员控制台( Developer console )
点击Debug | Open Execute Anonymous Window. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovCiC68A-1572587509251)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571104642554.png)]
运行代码:
①点击Execute(运行全部)
②点击**Execute Highlighted(运行选中部分代码) **
使用数据操作语言(缩写为DML)在Salesforce中创建和修改记录。使用数据操作语言(缩写为DML)在Salesforce中创建和修改记录。
insert
新增单条记录
// 创建一个客户对象 Account acct = new Account(Name='Acme', Phone='(415)555-1212', NumberOfEmployees=100); // 使用DML插入客户信息 insert acct; //插入记录时,系统会为每个记录分配一个ID。 //ID值还会自动填充到在DML调用中用作参数的sObject变量上。(也就是说将分配的ID赋值给对象) ID acctID = acct.Id; System.debug('ID = ' + acctID);批量新增
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')}; // Bulk insert all contacts with one DML call insert conList;update
批量修改
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')}; insert conList; 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); } } update listToUpdateUpsert
什么时候用Upsert?
如果你有一个包含新纪录和现有记录的集合,则可以使用Upsert来对所有记录进行插入和更新。
Upsert的优势?
如果集合的记录在数据库中存在,则会更新记录。如果集合的记录在数据库中不存在,则会新增记录。Upsert有助于避免重复记录的创建,并可以节省您的时间,因为您不必先确定哪些记录。
注意:Upsert语句通过比较一个字段的值将”操作的数据“和”数据库中的数据“作对比。
如果你没指定字段,Upsert使用当前对象的ID做对比。
①指定字段
upsert sObjectList Account.Fields.MyExternalId;//指定字段作为对比值②不指定字段
Contact josh = new Contact(FirstName='Josh',LastName='Kaplan',Department='Finance'); insert josh; josh.Description = 'Josh\'s record has been updated by the upsert operation.'; Contact kathy = new Contact(FirstName='Kathy',LastName='Brown',Department='Technology'); List<Contact> contacts = new List<Contact> { josh, kathy }; upsert contacts;Upsert对比结果:
如果字段值不匹配,则会创建一个新的对象记录。
如果字段值匹配一次,则现有对象记录将更新。
如果字段值多次匹配,则会生成错误,并且对象记录不会插入或更新。
delete
删除的记录不会从 Lightning Platform (数据库)上永久删除,但是会将它们放置在回收站中15天内,从那里可以恢复它们。
语法:
Contact[] contactsDel = [SELECT Id FROM Contact WHERE LastName='Smith']; delete contactsDel;如果DML操作失败,则返回类型为的异常 DmlException。您可以在代码中捕获异常以处理错误情况。
这个例子产生了一个 DmlException因为它尝试插入没有必填名称字段的帐户。异常捕获在catch块中。
try { Account acct = new Account(); insert acct; } catch (DmlException e) { System.debug('发生了DML异常: ' + e.getMessage()); }Apex包含内置的Database类,该类提供执行DML操作并镜像DML语句副本的方法。
这些数据库方法是静态的,并在类名上调用。
Database.insert()Database.update()Database.upsert()Database.delete()Database.undelete()Database.merge()与DML语句不同,数据库方法具有可选的allOrNone 参数,该参数使您可以指定操作是否允许部分成功。当此参数设置为false时,如果部分记录操作发生错误,则将提交成功的记录,并为失败的记录返回错误。此外,部分操作成功的记录不会引发任何异常。
Database.insert(recordList, false);数据库方法返回结果对象,其中包含每个记录的成功或失败信息。
Database.SaveResult[] results = Database.insert(recordList, false);默认情况下,allOrNone参数是true,这意味着Database方法的行为如果遇到失败,将抛出异常,回滚所有操作。
以下语句的效果一致
Database.insert(recordList); Database.insert(recordList, true); insert recordList;insert关联记录(需要多次DML)
Account acct = new Account(Name='SFDC Account'); insert acct; ID acctID = acct.ID; //将客户关联到对应的联系人(需要执行俩次DML) Contact mario = new Contact( FirstName='Mario', LastName='Ruiz', Phone='415.555.1212', AccountId=acctID); insert mario;update关联记录(需要多次DML)
Contact queriedContact = [SELECT Account.Name FROM Contact WHERE FirstName = 'Mario' AND LastName='Ruiz' LIMIT 1]; queriedContact.Phone = '(415)555-1213'; queriedContact.Account.Industry = 'Technology'; update queriedContact;//更新联系人信息 update queriedContact.Account; //更新联系人的客户信息删除关联记录
delete操作支持级联删除, 如果删除父对象,就会自动删除其子对象。
例如,删除帐户也将删除其相关联系人。
Account[] queriedAccounts = [SELECT Id FROM Account WHERE Name='SFDC Account']; delete queriedAccounts;SOQL对象查询语言
SOQL每次查询单个表的数据上限是200,如果超过200条数据,则会再发另外一个请求去查询剩余的数据。
您无需在查询中指定Id字段,因为它始终在Apex查询中返回,除非你只查询ID字段,这个时候则需要指定ID字段。
在查询编辑器中运行查询时,您可能还需要指定ID字段,因为除非指定,否则不会显示ID字段。
当SOQL嵌入Apex中时,称为内联SOQL。
Account[] accts = [SELECT Name,Phone FROM Account];您可以对大多数字段进行排序,包括数字和文本字段。您无法对富文本格式和多选选择列表之类的字段进行排序。
SELECT Name,Phone FROM Account ORDER BY Name ASC SELECT Name,Phone FROM Account ORDER BY Name DESC例如,此查询检索返回的第一个帐户。请注意,当limit 1的时候使用时返回的值是一个帐户而不是数组。
Account oneAccountOnly = [SELECT Name,Phone FROM Account LIMIT 1];使用:引用变量
String targetDepartment = 'Wingo'; Contact[] techContacts = [SELECT FirstName,LastName FROM Contact WHERE Department=:targetDepartment];当查询名称为"SFDC Computing"的客户时,同时查询出该客户对应的联系人
SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'SFDC Computing'使用关联查询出来的结果还能通过.调用相关联的数据
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);也可以从子表数据调用主表数据
Contact[] cts = [SELECT Account.Name FROM Contact]; Contact carol = cts[0]; String acctName = carol.Account.Name; System.debug('account name is ' + acctName);Salesforce对象搜索语言(SOSL)是一种Salesforce搜索语言,用于在记录中执行文本搜索。使用SOSL在Salesforce中跨多个标准和自定义对象记录搜索字段。文本搜索不区分大小写 。
语法:
//IN ALL FIELDS 默认查询全部字段所以可有可无 FIND {bo} RETURNING Contact FIND {bo} IN ALL FIELDS RETURNING Contact(name) //如果需要指定特定字段查找 FIND {bo} IN Email FIELDS RETURNING Contact(name) //跟在对象后面的字段是返回的内容 FIND {bo} IN ALL FIELDS RETURNING Contact(id,name) //Order by 排序某个字段 FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name) //limit设置返回的最大记录数 FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name LIMIT 10) //offset将起始行偏移量设置为结果 FIND {Cloud Kicks} RETURNING Account (Name ORDER BY Name LIMIT 10 OFFSET 25)这是一个SOSL查询的示例,该查询搜索具有任何字段带有单词“ SFDC”的字段的客户和联系人。
List<List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName)];案例:在客户的字段Name,联系人字段FirstName,LastName,Department,中查找值为Wingo的记录
FIND {Wingo} IN ALL FIELDS RETURNING Account(Name), Contact(FirstName,LastName,Department)SOSL与SOQL一样,SOSL允许您在组织的记录中搜索特定信息。与一次只能查询一个标准或自定义对象的SOQL不同,单个SOSL查询可以搜索所有对象。
另一个区别是,SOSL根据单词匹配来匹配字段,而默认情况下(当不使用通配符时)SOQL执行完全匹配。例如,在SOSL中搜索“数字”将返回字段值为“数字”或“数字公司”的记录,但SOQL仅返回字段值为“数字”的记录。
SOQL和SOSL是两种具有不同语法的独立语言。每种语言都有不同的用例:
使用SOQL检索单个对象的记录。使用SOSL在多个对象之间搜索字段。SOSL查询可以搜索对象上的大多数文本字段。Apex触发器使您可以在事件发生之前或之后对Salesforce中的记录(例如插入,更新或删除)执行自定义操作。就像数据库系统支持触发器一样,Apex为管理记录提供触发器支持。
您可以使用触发器来执行在Apex中可以做的任何事情,包括执行SOQL和DML或调用自定义Apex方法。
你可以在此页面上编辑、删除等操作
在触发器内部调用类方法,触发器除了可以在内部改变当前记录的值,还能调用外部的方法
//内部修改值 trigger HelloWorldTrigger on Account (before insert) { for(Account a : Trigger.New) { a.Description = 'New description'; } } //内部调用外部类方法 //当联系人有新纪录新增或删除时,调用邮箱类的方法发信息给管理员 trigger ExampleTrigger on Contact (after insert, after delete) { if (Trigger.isInsert) { Integer recordCount = Trigger.New.size(); // Call a utility method from another class EmailManager.sendMail('Your email address', 'Trailhead Trigger Tutorial', recordCount + ' contact(s) were inserted.'); } else if (Trigger.isDelete) { // Process after delete } }使用触发器添加关联记录
//当客户记录被新增或者修改时,遍历所有客户对象,如果当前客户没有关联的业务机会,则自动帮客户创建一个关联当前用户的业务机会 trigger AddRelatedRecord on Account(after insert, after update) { List<Opportunity> oppList = new List<Opportunity>(); // Get the related opportunities for the accounts in this trigger Map<Id,Account> acctsWithOpps = new Map<Id,Account>( [SELECT Id,(SELECT Id FROM Opportunities) FROM Account WHERE Id IN :Trigger.New]); // Add an opportunity for each account if it doesn't already have one. // Iterate through each account. for(Account a : Trigger.New) { System.debug('acctsWithOpps.get(a.Id).Opportunities.size()=' + acctsWithOpps.get(a.Id).Opportunities.size()); // Check if the account already has a related opportunity. if (acctsWithOpps.get(a.Id).Opportunities.size() == 0) { // If it doesn't, add a default 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; }有时您需要对某些数据库操作添加限制,例如在满足某些条件时阻止保存记录。
以下触发条件可防止删除具有相关业务机会的客户。默认情况下,删除客户会导致其所有相关记录的级联删除。此触发器可防止业务机会的级联删除。
trigger AccountDeletion on Account (before delete) { // Prevent the deletion of accounts if they have related opportunities. 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( 'Cannot delete account with related opportunities.'); } }Apex允许您调用Apex代码并将其与外部Web服务集成。对外部Web服务的Apex调用称为标注。
//标有@future(callout=true)的方法都是异步方法 public class CalloutClass { @future(callout=true) public static void makeCallout() { HttpRequest request = new HttpRequest(); // Set the endpoint URL. String endpoint = 'http://yourHost/yourService'; request.setEndPoint(endpoint); // Set the HTTP verb to GET. request.setMethod('GET'); // Send the HTTP request and get the response. HttpResponse response = new HTTP().send(request); } } //异步调用外部类方法 trigger CalloutTrigger on Account (before insert, before update) { CalloutClass.makeCallout(); }反例:查询每个客户的业务机会,需要发n次SOQL(n为客户数量)
trigger SoqlTriggerNotBulk on Account(after update) { for(Account a : Trigger.New) { // Get child records for each account // Inefficient SOQL query as it runs once for each account! Opportunity[] opps = [SELECT Id,Name,CloseDate FROM Opportunity WHERE AccountId=:a.Id]; // Do some other processing } }将Trigger.New里面所有的客户作为一个集,使用IN查询此集里满足条件的业务机会对象
trigger SoqlTriggerBulk on Account(after update) { // Perform SOQL query once. // Get the related opportunities for the accounts in this trigger, // and iterate over those records. for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity WHERE AccountId IN :Trigger.New]) { // Do some other processing } }反例:将DML操作写在for循环中,会极大的影响其效率。
trigger DmlTriggerNotBulk on Account(after update) { // Get the related opportunities for the accounts in this trigger. List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity WHERE AccountId IN :Trigger.New]; // Iterate over the related opportunities for(Opportunity opp : relatedOpps) { // Update the description when probability is greater // than 50% but less than 100% if ((opp.Probability >= 50) && (opp.Probability < 100)) { opp.Description = 'New description for opportunity.'; // Update once for each opportunity -- not efficient! update opp; } } }创建一个新的集合,装新的记录数据。遍历完之后再执行单个DML操作,操作集合。
trigger DmlTriggerBulk on Account(after update) { // Get the related opportunities for the accounts in this trigger. 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) { // Update the description when probability is greater // than 50% but less than 100% if ((opp.Probability >= 50) && (opp.Probability < 100)) { opp.Description = 'New description for opportunity.'; oppsToUpdate.add(opp); } } // Perform DML on a collection update oppsToUpdate; }编写Test类基本步骤可以分成4步:
1.创建测试数据;
2.调用Test.startTest()方法;
3.调用需要测试的方法();
4.调用Test.stopTest()方法。
使用==System.assertEquals()==验证。它有两个参数:第一个是期望值,第二个是实际值。
@isTest static void testWarmTemp() { Decimal number = 10/2; System.assertEquals(5,number); }也可以有三个参数,第一个是期望值,第二个是实际值,第三个是测试失败后提示的语句。
@isTest static void testBoilingPoint() { Decimal number = 10/2; // Simulate failure System.assertEquals(0,number,'number 不是预期的值,测试失败'); }等于运算符(==)执行不区分大小写的字符串比较
在单元测试中,建立代码测试覆盖100%,最低要求75%。
蓝色(覆盖)线和红色(未覆盖)线
如何全部覆盖?
再写一个测试方法保证他能进到红色的代码中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIdPdtnW-1572587509254)(https://res.cloudinary.com/hy4kyit2a/f_auto,fl_lossy,q_70/learn/modules/apex_testing/apex_testing_intro/images/4177c5d150c4add6e968944ddd37e9de_apex_testing_code_coverage_partial.png)]
我们需要在测试类本身创建测试类的数据。默认情况下,测试类不能访问组织数据,但是如果您设置@isTest(seeAllData = true),那么它将有权访问组织的数据。
在测试类中使用的DML,不会影响真实数据库的数据。并且你还能创建测试数据以便测试。如下:
@isTest public class TestDataFactory { public static List<Account> createAccountsWithOpps(Integer numAccts, Integer numOppsPerAcct) { List<Account> accts = new List<Account>(); for(Integer i=0;i<numAccts;i++) { Account a = new Account(Name='TestAccount' + i); accts.add(a); } insert accts; List<Opportunity> opps = new List<Opportunity>(); for (Integer j=0;j<numAccts;j++) { Account acct = accts[j]; // For each account just inserted, add opportunities for (Integer k=0;k<numOppsPerAcct;k++) { opps.add(new Opportunity(Name=acct.Name + ' Opportunity ' + k, StageName='Prospecting', CloseDate=System.today().addMonths(1), AccountId=acct.Id)); } } // Insert all opportunities for all accounts. insert opps; return accts; } }TestMethod关键字
单元测试方法是不带参数,不向数据库提交数据,不发送电子邮件,并在方法定义中使用testMethod关键字或isTest注释声明的方法。此外,测试方法必须在测试类中定义,即用isTest注释的类。
Test.startTest()和Test.stopTest()
这些是可用于测试类的标准测试方法。这些方法包含我们将模拟我们的测试的事件或动作。就像在这个例子中,我们将测试我们的触发器和帮助类来模拟火灾触发器,通过更新记录,我们已经做了开始和停止块。这也为在开始和停止块中的代码提供单独的调节器限制。
System.assert()
此方法用实际检查所需的输出。在这种情况下,我们期望插入一个发票记录,所以我们添加了assert来检查。
@isTest private class TestAccountDeletion { @isTest static void TestDeleteAccountWithOneOpportunity() { // Test data setup // Create one account with one opportunity by calling a utility method Account[] accts = TestDataFactory.createAccountsWithOpps(1,1); // Perform test Test.startTest(); Database.DeleteResult result = Database.delete(accts[0], false); Test.stopTest(); // Verify that the deletion should have been stopped by the trigger, // so check that we got back an error. System.assert(!result.isSuccess()); System.assert(result.getErrors().size() > 0); System.assertEquals('Cannot delete account with related opportunities.', result.getErrors()[0].getMessage()); } } `` private class TestAccountDeletion { @isTest static void TestDeleteAccountWithOneOpportunity() { // Test data setup // Create one account with one opportunity by calling a utility method Account[] accts = TestDataFactory.createAccountsWithOpps(1,1); // Perform test Test.startTest(); Database.DeleteResult result = Database.delete(accts[0], false); Test.stopTest(); // Verify that the deletion should have been stopped by the trigger, // so check that we got back an error. System.assert(!result.isSuccess()); System.assert(result.getErrors().size() > 0); System.assertEquals('Cannot delete account with related opportunities.', result.getErrors()[0].getMessage()); } }