Dynamic Apex 完全指南:Describe 信息、动态 SOQL/SOSL 与动态 DML
Dynamic Apex完全指南:涵盖Describe信息(Token轻量引用/getDescribe/describeSObjects)、字段Token使用(静态成员变量/fields/getMap动态映射)、权限考量(用户模式运行/字段隐藏)、Schema方法(describeTabs获取Tab元数据/getGlobalDescribe全局sObject映射/数据类别)、动态SOQL(Database.query/queryWithBinds绑定变量Map/accessLevel用户vs系统模式/不能使用绑定变量字段的限制/escapeSingleQuotes防注入/countQuery/getQueryLocator额外方法)、动态SOSL(search.query/Search.find+WITH SNIPPET搜索片段/SOSL注入防护)以及动态DML(newSObject动态创建/get+put字段操作/getSObject+putSObject外键处理/父子关系动态查询)。
本课程共有 10 个章节
同学们,今天咱们聊聊Salesforce里一个特别灵活的功能,叫做“动态Apex”。平时我们写的代码,对象、字段、查询语句都是固定写死的,对吧?但有时候,我们需要程序在运行的时候,根据情况自己决定去访问哪个对象、查什么字段,甚至拼出一句查询语句来。这就是动态Apex要做的事儿。 简单来说,动态Apex能让我们创建更加灵活的应用程序。它主要提供了这么几个能力: 第一,可以访问s对象和字段的“描述信息”。注意,这不是某一条具体数据,而是对象和字段本身的元数据,比如“这个字段是什么类型、有多长、能不能编辑”。有了这些信息,程序就能自己判断该怎么处理数据了。 第二,可以写出动态的SOQL和SOSL查询语句。平时我们写查询,都是提前写死的,比如[SELECT Name FROM Account]。但动态SOQL呢,就像是在代码里搭积木,可以在运行时用字符串拼接出查询语句去执行。这样,一旦需要查哪个对象、哪个字段不确定的时候,就可以动态生成查询。 第三,还可以用动态TLR,也就是通过程序动态创建记录。比如,你从一个外部系统收到一些数据,但不确定它到底要存进哪个对象里,你就可以在运行时根据规则决定创建什么类型的记录。 那这个能力在什么时候最有用呢?尤其是那些从AppExchange上安装的应用。因为这些应用要兼容各家客户的org,客户的字段可能不一样,对象也可能没创建出来。用动态Apex,应用就可以先探测一下环境,再根据实际情况去做查询,或者把数据存到相应的对象里,这样就不用为每个客户都定制一套代码了,非常灵活。 所以记住,动态Apex让我们的代码不再是“硬邦邦”的,而是能根据运行时的信息,自己去“思考”和适应,特别适合那些需要高通用性的场景。好了,这个点我们就讲到这,接下来可以看看具体的用法。
同学们,今天咱们来看一个很实用的话题——在Apex里,怎么去描述或者识别一个sObject,或者一个字段。 Apex给我们提供了两种轻量级的数据结构,再加上一个专门用来获取描述信息的方法。你可能会问,到底是哪两种数据结构呢?一种是,令牌,,也就是Token;另一种就是调用describeSObjects方法之后,返回给你的那个描述结果。咱们习惯把它直接叫作Describe结果。而那个专门的方法,就是,describeSObjects,。 先来看这个,令牌,。令牌是对一个sObject,或者一个字段的超轻量级引用。它非常精简,可以很方便地进行序列化,而且在编译的时候就会帮你验证——你写的这个对象或字段到底存不存在,一编译就知道了。因为它体积小,运行起来就,更快、更高效,。 什么时候用令牌呢?最常见的一个场景,就是你想判断某个sObject到底是不是“账户”对象。这时候,你完全不需要去调用厚重的describe方法,只要用令牌做个相等比较就行了。比如,你拿到一个sObject,用它的`getSObjectType()`取得类型令牌,然后直接跟`Account.SObjectType`用双等号(==)比一下,如果返回true,那它就是账户。像这样: ```apex if(myObj.getSObjectType() == Account.SObjectType) { // 这就是个账户对象 } ``` 是不是特别直接?因为它背后就是很轻量的引用比较,性能开销非常小。 那另一种方式,就是,describeSObjects方法,。这个方法是对sObject类型执行一个完整的描述,返回的结果是一个描述信息对象,里面包含了这个sObject所有的描述属性。比如它有哪些字段、字段是什么类型、能不能创建、能不能更新,等等,非常详尽。你可以把它想象成是一本厚厚的说明书,什么都查得到。 所以说,,令牌是快刀,describe是百科全书,。你只需要知道“这东西是不是账户”的时候,就用令牌去比;当你需要读取很多元数据去做逻辑判断、动态配置的时候,再去用describeSObjects。这样你的代码才能跑得又快又稳。 好,今天这个点很简单,但用好了能明显提升代码质量。下一次咱们再讲讲怎么遍历describe结果里的字段信息。
好,我们来看一下如何在Apex里访问一个字段的“标记”,也就是字段的元数据信息。你可以把它理解成字段的身份证,通过它我们能知道这个字段的各种属性。 首先,获得字段标记最简单的方法,就是直接用这个字段所在的sObject类型的静态成员变量。比如,Account.Description,这样就能拿到“账户”对象上“描述”这个字段的标记。另一种方法,如果你已经有了字段的描述结果,可以调用它的 getSObjectField() 方法,同样能得到这个标记。 那拿到标记之后,怎么去查看字段的详细描述呢?你只需要在字段标记上调用 getDescribe() 方法,或者直接访问这个字段的成员变量,就能得到字段的描述结果,里面包含了字段类型、长度、是否必填等等信息。 另外,如果你想把一个对象里所有的字段名字和它们的标记对应起来,可以用一个叫 getMap() 的方法。它会返回一个映射,键是字段名,值就是字段标记,这样你就可以很方便的通过名字找到想要的字段了。 但是要注意一点,对于个人账户,字段标记这种用法是不支持的,这时候你就老老实实用字段名字符串去操作。 还有一个小细节,当你在已安装的托管包里描述字段的时候,哪怕你的org没有启用Chatter,Chatter相关的字段也会出现在描述结果里,这是一个已知的行为。 总结一下,字段标记是我们在动态代码里操作字段的钥匙,掌握它的获取和使用,能让你的代码更灵活。
同学们,我们来看这一页内容。它讲的是Apex类在运行时的一个关键概念——默认的用户模式。 简单说,当你写Apex代码去查询或操作数据时,Salesforce会尊重当前用户对对象的权限,还有字段级别的安全设置。也就是说,用户能看到哪些字段,代码就只能访问哪些字段;用户没有权限看到的,代码也拿不到。 那在做“描述调用”的时候,比如你想获取一个对象有哪些字段、字段的标签、关系等等,如果对象或字段受到权限限制,系统就不会返回全部的信息,而是只返回当前用户有权限看到的那部分。 还有一个点,就是除了用我们熟悉的Schema.getGlobalDescribe()这些静态方法外,你还可以调用 Schema.describeSObjects() 这个方法,它接收的是你指定对象的API名称列表。调用之后会返回一个叫做 DescribeSObjectResult 数组的结果。这个结果里包含了对象的各种信息:比如标签、字段数量、是不是自定义对象、有哪些子关系等等。用这种方式来描述对象,同样会跟随用户模式的权限规则。 所以,你需要记住的就是:Apex默认在用户模式下运行,一切数据访问和描述信息都受权限控制。如果你想获取不受权限限制的全量描述信息,那就需要在类上标记为 without sharing 或者用系统模式来调用。但大部分场景下,默认的用户模式正是为了保证安全。 这样讲清楚了吗?有问题可以随时提出来。
各位同学,大家好!今天我们来看Apex当中,两个非常实用的方法,它们能够动态地帮我们拿到Salesforce应用程序和对象的元数据信息。 先看第一个:Schema.describeTabs()。 通过这个方法,你可以获取到当前组织中所有应用程序的信息,以及这些应用里包含了哪些选项卡。 对于每一个应用,你都能拿到它的标签、Logo的URL,还有它包含的选项卡列表。 而每一个选项卡,又会告诉你它关联的是哪个sObject,图标的URL是什么,选项卡的颜色,以及它到底是一个标准选项卡,还是自定义的。 这个功能在做动态菜单、自定义导航的时候特别有用。 接下来看第二个方法:Schema.getGlobalDescribe()。 它返回的是一个映射结构,把所有sObject的名称映射到对应的sObject令牌上。 有了这个映射,你在代码里就可以很方便地根据对象名,动态去获取它的元数据描述了。 这个方法生成的映射,是基于当前运行用户的权限来决定的,所以不同用户调用,看到的对象集合可能不一样。 从API 28.0版本开始,映射的键,也就是对象的名字,总是会以命名空间开头,前提是这个对象有命名空间的话。 这里还有一个细节:如果你是从一个已安装的托管包里调用getGlobalDescribe,即使这个安装组织没有启用Chatter,方法返回的映射里,也依然会包含Chatter相关的sObject,比如协作组这些对象。 这样设计,是为了保证托管包里的代码,不管在什么样的组织里,都能可靠地引用到这些基础对象。 好了,今天这两个方法的讲解就到这里,大家可以打开开发者控制台,亲自动手试一试,感受一下它们返回的数据结构。我们下节课再见!
同学们,我们来看今天这一页,讲的是动态SOQL。动态SOQL其实就是我们在Apex代码里,在运行的时候,自己去拼接一个SOQL查询字符串,然后用它去查数据。 实现动态查询,主要靠两个方法:一个是 `Database.query()`,另一个是 `Database.queryWithBinds()`。都是用来执行我们拼出来的那个SOQL字符串。 有一个要注意的点,从API 55.0版本开始,这两个方法都支持一个 `accessLevel` 参数,可以用它来指定是以用户模式还是系统模式来运行这个查询。 接着我们来看绑定变量的使用。动态SOQL里可以写简单的绑定变量,但是你不能再对这些绑定变量使用点号去访问字段,比如 `:acc.Name` 这样就不行了。你需要先把字段值取出来,放到一个单独的变量里,再传进去。 如果你用的是 `queryWithBinds`,它会从一个Map里读取绑定变量的值。这里要注意命名规则,Map里的键必须以ASCII字母开头,不能是数字开头,当然也不能使用SOQL的保留关键字。 简单来说,把字段值先存到简单变量里,安全又灵活。这样你就能在运行时构建出你想要的查询了。好,这一页就这些,大家消化一下。
好,咱们接着往下看,这一页主要讲的是动态 SOQL 的几个关键点,做开发的时候经常会用到,一定要记牢。 首先,安全永远是第一位。如果我们的查询语句里拼接了用户输入的内容,就很容易被 SOQL 注入攻击。怎么防呢?就是用 `escapeSingleQuotes` 这个方法。它会把用户提供的字符串里所有的单引号前,都加上一个转义字符,这样恶意代码就不会被执行了。这是个特别基础但也特别重要的好习惯。 接下来,我们有时候需要提前知道,我这个动态查询到底会查出多少条记录。如果是静态查询,你可能直接用 `查询.size()` 或者写在循环里计数,但动态查询更方便的做法是直接调用 `Database.countQuery`。你把那个动态生成的查询字符串传进去,它直接返回给你记录的总数,非常直观。 再一个,在写批处理 Apex 或者做 Visualforce 分页的时候,我们经常在 Batch 的 `start` 方法里使用 `Database.getQueryLocator`。这个方法能根据你给的动态查询字符串,返回一个查询定位器,这样平台就能高效地分块处理海量数据了。注意,这里不是静态直接把查询写死,而是用动态字符串传入,这一点很灵活。 还有一个容易踩坑的地方,就是变量绑定的限制。写静态 SOQL 的时候,我们可以在查询里直接用 `:myVariable.field__c` 这种写法来引用字段。但在动态 SOQL 里,你可不能这么干。你必须先把那个字段的值取出来,放进一个单独的普通变量里,比如 `String accName = myVariable.Name;`,然后把 `accName` 再拼接到查询字符串中。这一点一定要记住,否则代码会报错。 最后,别忘了,动态 SOQL 跟静态查询一样,都受到 Salesforce 各种调控器限制。比如总共能查询的记录数、查询执行时间等等,限制是完全一样的,不会因为你写的是动态就放松。所以该优化的还是得优化,别指望它能绕过系统底线。 把这几点都掌握好,动态 SOQL 用起来就很得心应手了。
同学们,今天我们来聊聊动态SOSL。所谓动态SOSL,就是在程序运行时,用Apex代码自己去拼接SOSL搜索语句,而不是像静态查询那样直接写在方括号里。这样做的好处是,你可以根据用户输入或业务逻辑,灵活地构建搜索条件。 最常用的做法是调用 `Search.query` 方法,把拼好的SOSL字符串传进去。它会返回一个“列表的列表”,也就是 `List<List<SObject>>`。外层列表对应不同的查询子句,内层列表就是搜索到的记录。 另外,如果你需要获取搜索结果中出现的上下文短文段,可以使用 `Search.find` 方法。这个方法会返回 `Search.SearchResult` 对象,接着调用它的 `get` 方法来提取某个指定对象类型的结果,再在每个结果上使用 `getSnippet` 方法,就能拿到那段高亮的上下文文本了。 做动态拼接时,一定要小心SOSL注入的问题,尤其是用户输入的内容可能包含恶意单引号。Apex提供了一个 `escapeSingleQuotes` 方法,记得用它来转义这些特殊字符,保证查询安全。 在系统资源使用上,动态SOSL和静态SOSL受到的限制是一样的,比如每笔查询返回的行数上限、搜索的对象上限等等,都遵循相同的调控器规则,所以不用担心资源分配的不公平。 好了,掌握了这些,你就可以在需要的时候,用动态方式去构建功能强大的全局搜索了。
同学们好,这节课我们来说说 Apex 里怎么动态地创建和操作 sObject。这可比在运行时查描述信息再拼 SOQL 还要直接。 你可能会想,不写 SOQL 也能造记录吗?可以的。用 ,sObject 类型, 上的 `newSObject` 方法就行。比如 `Account.sObjectType.newSObject()`,这就给你生成了一个空的客户记录。不过要注意,这个方法的返回是个通用的 sObject,你得把它,转换,成具体的类型——比如 `Account`,才能当客户对象继续用。 如果你在 newSObject 时直接传入一个记录 ID,比如 `newSObject('001...')`,那你拿到的就是一个引用已有记录的 sObject,可以直接给它改值做更新,非常省事。 那么字段值怎么读、怎么写呢?通用的 ,get, 和 ,put, 方法就派上用场了。`get('Name')` 能取出名字,`put('Name', '新名字')` 就能把名字设进去。这里的参数可以是字段的 API 名称字符串,也可以直接传字段标记(像 `Account.Name`),内部是一样的效果。 那碰到关联关系,特别是想通过,外部 ID, 来关联父记录怎么办?这时就要用到 ,getSObject, 和 ,putSObject, 了。举个例子,你有一个订单,它有个外部 ID 字段叫 `ExternalCustomerId__c`,你想把它指向一个客户,但你手里只有客户的外部 ID 值,没有 Salesforce 的 ID。那你就可以构建一个只设了外部 ID 的客户 sObject,然后用 `putSObject` 把它塞进订单的关系字段里,系统会自动按外部 ID 去匹配。这样你就不用先花一次 SOQL 去查客户的 ID,代码少、效率高。 总结一下,灵活运用 newSObject、get/put 和 getSObject/putSObject,就能让咱们的动态代码更简洁,也更强大。好,这节课就到这里,大家试着动手写写看吧。
好,今天我们来讲一下,在处理外部密钥和相关记录的时候,要用到几个很实用的方法。内容不多,但很容易弄混,我们慢慢梳理。 首先,如果你只是想设置或者读取一个外部密钥字段里存的“纯量ID值”,比如一个外键字段存的是另一个表的主键,用最普通的 `get()` 和 `put()` 方法就够了。就像从字段里直接拿一个文本或者数字一样简单。 但如果你想操作的是那个外键关联的,整条记录对象,,就不能用 `get()` 了。这时候要用 `getSObject()` 把那条关联的记录取出来,用 `putSObject()` 把一条记录对象放进去。注意了,这两个方法专门处理的是“关系字段”,返回的或者需要的参数都是 sObject 类型的数据。 还有一个容易忽略的地方,就是当你处理子对象的时候,你不需要特意为父对象指定外部 ID。就算你在父对象里面放了一个 ID 字段,TLR 操作也会自动忽略它,不会拿它当外部键来做关联。所以你的代码可以更简单,不用担心重复设置的问题。 最后,如果你想在动态查询中,一次性拿到父子关系里的子记录,可以用 `getSObjects()` 方法。它能帮你把关联的子记录列表直接取出来,就像拿到一个集合一样,遍历起来非常方便。 简单总结一下:普通外键值用 `get/put`,关联记录对象用 `getSObject/putSObject`,动态查询拿子记录用 `getSObjects`。记住这个区别,代码就不容易写乱了。