DEX455

Apex sObject 集合完整指南

课程介绍

Apex sObject集合完整指南:覆盖sObject的List(声明/从SOQL自动填充/批量处理/重复引用陷阱/数组表示法)、sObject列表默认排序四步序列(类型标签→Name→标准字段→自定义字段/Unicode排序/空字段优先)、自定义排序(Comparator接口/Comparable包装类/Collator语言环境排序)、展开sObject和List表达式(链式访问)、sObject的Set(字段值比较唯一性/字段变更后无法找到)、sObject的Map(从SOQL自动填充/内存内联表连接/Map方法)以及sObject作为Map键的关键陷阱(ID自动填充导致键失效/触发器前后上下文差异)。

课程章节

本课程共有 8 个章节

  • 1

    sObject Collections

    第 194 页

    同学们,我们来看看这一页Slide,它介绍的是在Apex里如何用列表、集合和地图来管理sObject,也就是我们的数据记录。这是非常基础但又特别实用的技巧。 首先说列表。你肯定经常用列表来装一堆sObject,比如存放查询结果,或者准备批量更新、插入的数据。这样做最大的好处是能够一次性处理多条记录,既高效又不会超过Salesforce的调控限制。另外,列表里的sObject还可以排序。不过你要注意,直接在列表上调sort方法,对sObject不太管用,通常我们会在写SOQL查询时就加上`ORDER BY`,让数据库按字段排好序再返回;或者用Apex自定义排序逻辑。总之,列表可以帮你灵活地组织和排序数据。 接下来是集合。Set集合也能放sObject,它有一个关键特性:里面不会有重复的记录。它是怎么判断重复的呢?通过比较sObject的字段值。比如说,你往Set里加一个联系方式sObject,如果所有字段值和已有的一个完全一样,它就不会再存进去了。这特别适合用来做去重。比如你从好几个来源拿到了联系人,把他们都放进一个`Set<Contact>`,出来的结果就自动去掉重复的了。 最后一个是地图。

    查看详情
  • 2

    Lists of sObjects

    第 195 页

    同学们,我们来看一下关于 sObject 列表的几个要点,我会尽量说得简单好懂。 首先,Apex 里的列表(List)不光能存普通的数字、字符串这些简单类型,还能存各种各样的 sObject,比如客户(Account)、联系人(Contact)这些业务对象。这种 sObject 列表最大的好处就是能做,批量处理,。 打个比方,你用 SOQL 从数据库里查出一堆记录,直接把它们都放进一个列表里。接下来,如果你想把这些记录都插入到数据库里,或者批量更新,只需要对这一个列表执行一次 DML 操作(比如 `insert`)就行了,不用一条一条去循环处理,效率高很多。 当你执行插入操作的时候,Apex 会自动为列表里每一条新记录生成唯一的 ID,也就是 Salesforce 里的记录号,你不需要自己操心。 但是有一点需要特别注意:,列表里不能有重复的同一个对象实例,。举个例子,你创建了一个 Account 对象,然后把它的引用在列表里添加了两次,即使这个对象还没有 ID,还没存进数据库,这种列表在插入或更新时也会报错,系统不允许这么做。简单说,就是列表里的每个 sObject 都必须是独立的对象实例,不能把一个对象重复添加。 顺便说一下,声明 sObject 列表有两种写法,效果完全一样。一种是用标准的 List 写法,比如: ```apex List<Account> myAccounts = new List<Account>(); ``` 另一种是用数组式的方括号写法,像这样: ```apex Account[] myAccounts = new Account[]{}; ``` 两种你都可以用,看习惯就好。 总结一下: - 列表可以存 sObject,适合做批量 DML 操作; - 插入后系统自动分配 ID; - 列表里不能有重复的同一个对象实例; - 声明时既可以用 `List<...>`,也可以用数组符号 `[]`。 好,这就是关于 sObject 列表的基础内容,你理解了吗?

    查看详情
  • 3

    Sorting Lists of sObjects — Default Order

    第 196 页

    同学们,今天咱们来看看在 Apex 里怎么给一个 sObject 列表做排序。很简单,你直接调用 List 的 `sort` 方法就可以,但是内部有一套比较特别、分步骤的比较规则,咱们得理解它,不然结果可能会出乎意料。 首先记住,这个排序是区分大小写的。对 sObject 来说,它不是只盯着某一个字段排队,而是像走一套“闯关”流程:从第一步开始,只要分出高下,就马上停止,后面步骤全都不管了。 第一步,它会比较两个对象的,类型标签,,也就是 sObject 类型显示出来的名字,比如 “客户” 或 “联系人”。如果标签不一样,那就用 Unicode 顺序比出大小,直接排好;只有标签相同,才进入下一步。 第二步,如果这个对象有“名称”字段(像 Account 的 Name 字段),就会比较名称字段的值。注意,这步也是 Unicode 顺序,空值会排在非空值的前面。分出大小就停,分不出才继续。 第三步,开始比较,标准字段,,但是 Id 和 Name 这两个标准字段会被跳过,不参与这轮通用比较。比较时,是按照字段的 API 名称字母顺序,一个字段一个字段地来。比如先比 AnnualRevenue,再比 BillingCity,以此类推。每个字段的值同样采用 Unicode 排序,空字段在前。 第四步,如果标准字段全比完了依然平手,终于轮到,自定义字段,了。也是按字段 API 名的字母顺序,依次比较字段值,规则跟前面完全一样。 总的来说,你调用 `sort` 后,它会从上到下、从左到右把对象过一遍,只要在任何一个字段上发现不同,就立刻拍板定顺序。文本比较全部用 Unicode 规则,空字段永远跑到最前头。理解这套机制,你就能更好地控制列表中对象的展示顺序了。

    查看详情
  • 4

    Custom Sort Order of sObjects

    第 197 页

    好,今天我们来看一个很实用的小技巧:怎么给 sObject 列表做自定义排序。 假如你有一个客户列表,想按照客户的行业、名字或者一个自定义字段来排,但直接用 List.sort 只能按默认顺序,比如 ID 或你取出来的顺序,这就很受限。这时候,Salesforce 给我们提供了两种灵活的做法。 第一种,实现 Comparator 接口。你可以单独写一个类,实现 Comparator 接口,在里面定义自己的比较逻辑,然后把这个比较器对象传给 List.sort 方法。这样,列表就会按你的规则重新排列。比如,你想按客户的年收入从高到低排,就可以在 compare 方法里写好逻辑。 第二种,如果你不想额外写一个比较器类,也可以为 sObject 创建一个包装器类,让这个包装器实现 Comparable 接口。包装器里放着那个 sObject 实例,然后你在 compareTo 方法里写自定义的排序逻辑。之后你把 sObject 包装成包装器列表,调用 sort,就能按你的逻辑排了。这种方法也很直观。 另外,如果排序要考虑到不同地区的语言、文字顺序,比如中英文混合,或者带变音符号的字母排序,可以用 Collator 类。它会帮我们处理区域相关的比较规则,让排序结果更符合当地人的阅读习惯。 简单总结,就是三个要点:Comparator 适合给现有列表外加排序规则;Comparable 包装类适合让对象自带排序能力;Collator 解决全球化排序问题。这几招在项目里经常用到,可以轻松搞定复杂排序需求。

    查看详情
  • 5

    Expanding sObject & List Expressions

    第 198 页

    各位同学,今天咱们聊一个让代码变得更简洁的小技巧——用方法引用和列表表达式来玩转 sObject。 你之前可能写过这样的代码:查询返回一个列表,然后取第一个,再拿它的某个字段,最后调字段的方法。比如取联系人名字的大写形式,通常要分好几步,又拆变量又拆方法。但 Apex 其实支持把它们串成“一条龙”,写在一行里,就像搭积木一样。 我们来看这个神奇的链式写法:你直接 new 一个只包含一个 sObject 的列表,后面紧接着用 [0] 拿到那个元素,点出字段,最后再点出字段的方法。比如: `new List<Contact>{ myContact }[0].Name.toUpperCase();` 这行代码干了四件事:创建列表、取第一个元素、访问 Name 字段、把名字变成大写。一步到位,紧凑又清晰。 这就是列表表达式扩展的威力。你可以在创建列表的同时就做索引访问,然后再钻到字段和方法里去。这特别适合在查询后要快速拿某个值做转换的场景,省掉临时变量,代码看起来也更有表达力。 当然,这种写法容易让人眼花缭乱,所以建议只用在简单的取值转换上。它背后的原理还是那个规则:表达式可以嵌套,列表初始化本身就是一个表达式,接着当然可以继续点操作符。 总之,记住这个套路:new List<sObject>{…}[0].字段.方法(),它让你的代码从“平铺直叙”变得像一句精巧的口诀。以后写 Apex 时,可以试着用起来,会让同行觉得你代码很老道。 好,这一小节就到这里,我们下节继续聊聊其他省心表达式。

    查看详情
  • 6

    Sets of Objects

    第 199 页

    好,同学们,咱们这节课来聊聊Apex里的集合,特别是你把sObject放进集合时,会有一个很有趣,但也很容易踩坑的地方。 你们应该都知道,集合(比如Set)就是用来存不重复元素的容器。可以存数字、字符串,也可以存sObject。但sObject这种“对象”怎么判断是不是重复呢?它不是看内存地址,而是直接比较你对象里的,字段值,。 比如说,你现在有一个Account的集合。你创建了两个Account对象,都只设置了Name为“张三”,其他字段都空着。你把这两个对象都加到集合里,结果集合里只会有,一个,“张三”的Account,因为它们的字段值是一样的,Set就认为它们是重复的。 但是,如果你给第二个Account多设置了一个字段,比如Description(描述),一个写“客户A”,一个写“客户B”。那这时候,虽然Name相同,但整个对象的值不一样了,Set就会认为这是两个不同的记录,,两个都会加进去,。 好,这一点明白了,咱们接着说个更“坑”的地方,也是官方文档特别警告的。 如果你把一个sObject对象加到集合里之后,,又去修改了这个对象的字段值,,那惨了,这个集合里就“找不到”它了。比如说,你有一个Account a,Name=“李四”,加进了Set。然后你代码里又写了一句 `a.Name = '王五';` 这时候你用 `set.contains(a)` 去判断,会返回false,因为集合是用字段值来记忆的,它里面存的是“李四”,没有“王五”,而你提供的a已经是“王五”了,所以找不到。哪怕这个对象就是原来放进去的那个对象,也没用。 这就像你把一张写有“苹果”的纸条放进一个盒子里,然后你偷偷把纸条上的字改成了“香蕉”。当你拿着这张纸问盒子:“你有没有我这张纸上的东西?”盒子一看,上面写的是“香蕉”,但它里面只记得有“苹果”,所以就说没有。 所以,在写代码的时候一定要注意:如果你把一个sObject放进Set或者Map的键里,最好就别再去改它的字段了。否则contains、containsAll这些方法会给你意想不到的结果,查个半天都找不出bug。 好了,这个知识点就讲这么多,大家记住了吗?集合检查唯一性是靠字段值,而且对象值一旦变了,在集合里就“隐形”了。下节课我们再讲其他细节。

    查看详情
  • 7

    Maps of sObjects

    第 200 页

    同学们,我们继续来看这一页幻灯片,讲的是 Apex 里 Map 集合的一个很实用的特性。 映射,也就是 Map,它的键和值可以是任何数据类型,甚至可以是 sObject 类型。那这有什么好处呢?一个特别常见的场景,就是用来实现两个表在内存里的“连接”。比如我们有一个联系人的列表,每个联系人都关联了一个客户 ID。如果我们想在不反复查询数据库的情况下,快速根据客户 ID 拿到完整的客户记录,就可以先把所有相关的 Account 查出来,塞进一个 Map<Id, Account> 里。然后遍历联系人时,用联系人的 AccountId 直接从 Map 里取 Account,这就相当于在内存里做了一个表连接。 那怎么高效地构建这样的 Map 呢?你可以直接在 SOQL 查询的时候,利用查询结果来自动填充 Map。写法很简单:你写一个 SOQL 查询,把返回的 List 直接传给 Map 的构造方法。但是要注意一点:这时 Map 的键类型必须是 ID 或者 String,值类型必须是 sObject。比如,我们可以这样写: Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id, Name FROM Account]); 这里 new 运算符拿到的就是查询返回的 Account 列表,它会自动把每条记录的 Id 作为键,把整个 sObject 作为值,放进 Map 里。是不是很方便?这样你就不用自己手动写循环去 put 了。 最后,幻灯片底下还有一个特别提醒,跟咱们在多社区环境里查询用户记录有关。如果你用的是 Experience Cloud 站点,以前叫社区,你会发现,当一个用户同时属于多个社区网络时,某些自动检索操作可能会返回重复的用户 ID。比如说,通过 Apex 去拿“最近查看的社区成员”这种列表,因为同一个用户在不同的网络里可能被视为不同的记录,导致出现相同的 ID。那如果你试图把这个列表直接塞进一个以 ID 为键的 Map,就会因为键重复而报错。所以遇到这种情况,你需要额外的去重处理,不能直接依赖 Map 的自动构造。 好,这一页我们就讲到这里。记住:用 SOQL 结果直接构建 ID 到 sObject 的 Map 是非常高效的内存连接手段,但要留意键的唯一性,尤其是在多网络环境下处理用户记录的时候。

    查看详情
  • 8

    sObject Map Key Considerations

    第 201 页

    同学们,今天我们来聊一个在Apex中特别容易踩坑的小细节,就是拿sObject对象直接当Map的键来用。你可能觉得,把一个记录放进Map里,回头再拿同样的记录去取,肯定能取出来,对吧?但实际情况可能让你大跌眼镜。 我举个例子:假设你有一个联系人Contact对象,你创建它,没有Id,字段都是空的,然后你把它作为键,放了一个值进Map。接着,你可能把这个联系人插入到了数据库里,这下可好,系统自动给你的联系人填上了Id、CreatedDate这些字段。等你再拿着这个已经有Id的联系人去Map里找之前放进去的值,结果返回的是null,找不到了。明明还是那个对象,为什么找不到呢? 原因在于,Apex中的Map,当你用sObject当键的时候,它比较的不是对象的引用地址,而是对象身上所有字段的值。只要任何一个字段的值不一样,Map就认为这是两个不同的键。你最初放进去的那个对象,字段都是空的;插入数据库之后,Id、CreatedDate这些系统字段多出来了,相当于整个对象的字段值变了,所以Map就不认识它了。 这种情况在触发器里特别常见。比如你在一个before触发器里,从Trigger.New拿到了一批崭新的记录,这些记录都还没Id呢。你把它们当作键存进Map。等进了after触发器,同一条记录已经被提交,系统字段全填上了,这时候你再用它们去Map里取,一样取不出来。因为在Map眼里,before触发器和after触发器里的同一个sObject,字段值不一样,就不是同一个键。 所以我们的最佳实践是:如果需要拿记录做映射,尽量用记录的唯一标识,也就是Id字段,作为Map的键。比如说Map<Id, Contact>,而不是Map<Contact, Something>。用Id就不会受其他字段变化的影响,因为它一旦生成就稳定不变了。 总结一下,用sObject当Map键时要非常小心,因为键匹配是对比所有字段值,只要字段一变,键就失效。尤其是要注意系统字段自动填充的时机。安全起见,优先用Id做键。这个小细节记住了,以后可以少踩很多坑。 好,今天就讲到这儿,我们下个小节见。

    查看详情