精通 Salesforce 自定义对象:面向开发人员的 Apex 与 SOQL 指南
背景与应用场景
作为一名 Salesforce 开发人员,我们的日常工作远不止处理标准的 Account、Contact 或 Opportunity 对象。Salesforce 平台最强大的功能之一就是其高度的可扩展性,而这种扩展性的核心便是 Custom Objects (自定义对象)。标准对象 (Standard Objects) 构成了 CRM 的通用基础,但每个企业的业务模式都是独一无二的。当标准对象无法满足特定的业务数据存储需求时,自定义对象就成为了我们构建定制化解决方案的基石。
想象一下以下场景:
- 教育机构:需要一个“学生”对象 (Student__c) 来存储学号、专业、入学日期等信息,并与“课程”对象 (Course__c) 建立关联。
- 房地产公司:需要一个“房产”对象 (Property__c) 来管理楼盘信息,包括地址、面积、价格、户型图等,并关联到潜在的“买家” (Contacts)。
- 医疗系统:需要“病人”对象 (Patient__c) 和“病历”对象 (Medical_Record__c) 来跟踪患者的健康状况和就诊历史。
在这些场景中,强行使用标准对象会导致数据模型混乱、难以维护。自定义对象则允许我们为这些独特的业务实体创建专门的数据表,并定义它们自己的字段、关系、页面布局和业务逻辑。对于开发人员而言,自定义对象不仅是数据的容器,更是我们通过 Apex、SOQL 和 Lightning Web Components 等技术进行交互、实现复杂业务流程的核心实体。
原理说明
从开发人员的视角来看,理解自定义对象的底层原理至关重要。这不仅仅是在设置菜单里点击几下那么简单,它关乎到我们在代码中如何高效、准确地与这些数据结构进行交互。
API 名称 (API Name)
每个自定义对象和自定义字段都有一个标签 (Label) 和一个 API 名称 (API Name)。标签是用户在界面上看到的名称,可以随时更改。而 API 名称则是我们在代码中引用的唯一标识符。自定义对象和字段的 API 名称总是以 `__c` 结尾(c 代表 custom)。例如,一个标签为“书籍”的自定义对象,其 API 名称可能是 `Book__c`。在编写 SOQL 查询或 Apex 代码时,我们必须严格使用 API 名称。
例如:`SELECT Name, Author__c FROM Book__c WHERE ISBN__c = '978-7-111-54413-7'`
这里的 `Book__c`, `Author__c`, 和 `ISBN__c` 都是 API 名称。
关系 (Relationships)
自定义对象可以通过关系字段与其他对象(标准或自定义)连接起来,形成复杂的数据模型。作为开发人员,我们主要关注两种关系:
- 查找关系 (Lookup Relationship):这是一种较为松散的父子关系。子对象上的查找字段指向父对象。在代码中,我们可以通过关系名称(通常是字段 API 名称去掉 `__c` 并加上 `__r`)来访问父对象的字段。例如,如果 `Book__c` 上有一个指向 `Publisher__c` 的查找字段 `Publisher__c`,我们可以这样查询:
`SELECT Name, Publisher__r.Name FROM Book__c` - 主从关系 (Master-Detail Relationship):这是一种更紧密的父子关系。子(Detail)记录的生命周期、所有权和共享权限都由父(Master)记录控制。如果删除了主记录,所有从属记录也会被级联删除。这种关系在数据模型上提供了更强的完整性保证。
在 Apex 中的表示
在 Apex 中,每个 Salesforce 对象(无论是标准还是自定义)都对应一个 SObject 类型。我们可以像实例化一个普通的类一样来创建自定义对象的实例。例如,要创建一个 `Book__c` 记录,我们可以这样做:
Book__c newBook = new Book__c(); newBook.Name = '深入浅出 Salesforce'; newBook.Author__c = '某位专家';
这种强类型的编程方式使得代码更易读、更安全,因为编译器可以在编译时就捕获到字段名拼写错误等问题。
示例代码
以下代码示例展示了作为开发人员如何通过 Apex 对一个名为 `Book__c` 的自定义对象(包含 `Name`, `Author__c`, `Published_Date__c` 字段)执行基本的 CRUD (Create, Read, Update, Delete) 操作。这些示例严格遵循 Salesforce 官方文档中的 DML (Data Manipulation Language) 和 SOQL (Salesforce Object Query Language) 语法。
1. 创建 (Create) 自定义对象记录
以下代码创建了一个 `Book__c` 对象的新实例,并将其插入到数据库中。
// DML Insert Example // 官方文档参考: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_insert.htm // 1. 实例化一个自定义 SObject Book__c newBook = new Book__c( Name = 'Apex 设计模式', Author__c = 'Salesforce大学', Published_Date__c = Date.newInstance(2023, 10, 1) ); try { // 2. 使用 DML insert 语句将记录插入数据库 insert newBook; // 3. 插入成功后,记录的 Id 字段会被自动填充 System.debug('成功创建书籍记录,ID为: ' + newBook.Id); } catch (DmlException e) { // 4. 捕获可能发生的 DML 异常,例如必填字段缺失 System.debug('创建书籍记录失败: ' + e.getMessage()); }
2. 查询 (Read) 自定义对象记录
使用 SOQL 查询特定条件的 `Book__c` 记录。
// SOQL Query Example // 官方文档参考: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_soql_select.htm // 1. 创建一个 List 来存储查询结果,这是一个最佳实践 List<Book__c> books = [SELECT Id, Name, Author__c, Published_Date__c FROM Book__c WHERE Author__c = 'Salesforce大学' ORDER BY Published_Date__c DESC LIMIT 10]; // 2. 遍历查询结果并处理 if (!books.isEmpty()) { for (Book__c book : books) { System.debug('书名: ' + book.Name + ', 作者: ' + book.Author__c); } } else { System.debug('未找到符合条件的书籍。'); }
3. 更新 (Update) 自定义对象记录
先查询出需要更新的记录,修改其字段值,然后执行 DML update 操作。
// DML Update Example // 官方文档参考: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_update.htm // 1. 首先通过 SOQL 查询需要更新的记录 List<Book__c> booksToUpdate = [SELECT Id, Name FROM Book__c WHERE Name = 'Apex 设计模式' LIMIT 1]; if (!booksToUpdate.isEmpty()) { // 2. 获取第一条记录并修改字段值 Book__c bookToUpdate = booksToUpdate[0]; bookToUpdate.Name = '高级 Apex 设计模式'; // 修改书名 try { // 3. 执行 DML update 操作 update bookToUpdate; System.debug('书籍记录更新成功。'); } catch (DmlException e) { System.debug('更新书籍记录失败: ' + e.getMessage()); } }
4. 删除 (Delete) 自定义对象记录
查询到需要删除的记录,然后执行 DML delete 操作。
// DML Delete Example // 官方文档参考: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_dml_delete.htm // 1. 查询要删除的记录 List<Book__c> booksToDelete = [SELECT Id FROM Book__c WHERE Name LIKE '%测试%']; if (!booksToDelete.isEmpty()) { try { // 2. 执行 DML delete 操作 delete booksToDelete; System.debug('成功删除 ' + booksToDelete.size() + ' 条测试书籍记录。'); } catch (DmlException e) { System.debug('删除书籍记录失败: ' + e.getMessage()); } }
注意事项
在以编程方式处理自定义对象时,必须牢记 Salesforce 平台的多租户架构和限制。
权限与安全性
Apex 代码默认在系统模式 (System Mode) 下运行,会忽略对象级权限 (Object-Level Security) 和字段级安全性 (Field-Level Security, FLS)。但是,为了构建安全的应用程序,我们必须主动检查用户的权限。可以使用 `with sharing` 或 `inherited sharing` 关键字来强制执行记录级别的共享规则。对于 FLS,应在执行 DML 或 SOQL 之前进行显式检查。
代码示例 - 检查字段可访问性:
// 在查询前检查用户是否有权访问 Author__c 字段 if (Schema.sObjectType.Book__c.fields.Author__c.isAccessible()) { List<Book__c> books = [SELECT Author__c FROM Book__c LIMIT 1]; // ... process results } else { System.debug('当前用户没有权限访问 Author__c 字段。'); // 优雅地处理错误,而不是抛出异常 }
API 限制与批量化 (Bulkification)
Salesforce 强制执行严格的执行限制 (Governor Limits),以确保资源公平分配。例如,单个事务中 SOQL 查询不能超过 100 次,DML 操作不能超过 150 次。因此,严禁在循环中执行 SOQL 查询或 DML 操作。正确的做法是使用集合(如 List 或 Map)来批量处理数据。
错误示例 (不要这样做!):
List<Id> bookIds = getBookIds(); for (Id bookId : bookIds) { Book__c b = [SELECT Name FROM Book__c WHERE Id = :bookId]; // 在循环中执行 SOQL b.Name = b.Name + ' (V2)'; update b; // 在循环中执行 DML }
正确示例 (批量化):
List<Id> bookIds = getBookIds(); List<Book__c> booksToUpdate = [SELECT Name FROM Book__c WHERE Id IN :bookIds]; // 一次 SOQL 查询 for (Book__c b : booksToUpdate) { b.Name = b.Name + ' (V2)'; } update booksToUpdate; // 一次 DML 操作
错误处理
DML 操作可能会因为验证规则、触发器逻辑或数据库限制而失败。除了使用 `try-catch` 块捕获异常外,还可以使用 `Database` 类的方法,如 `Database.insert(records, allOrNone)`。当 `allOrNone` 设置为 `false` 时,即使部分记录失败,成功的记录也会被提交。返回的 `Database.SaveResult` 对象数组可以用来详细检查每条记录的成功或失败状态。
总结与最佳实践
自定义对象是 Salesforce 平台的核心,也是我们开发人员构建强大、可扩展应用程序的画布。它们提供了超越标准 CRM 功能的无限可能性,让我们能够精确地映射和自动化任何业务流程。
作为开发人员,与自定义对象交互时,应遵循以下最佳实践:
- 始终使用 API 名称:在所有代码(Apex, SOQL, LWC JavaScript)中,坚持使用以 `__c` 结尾的 API 名称,而不是易变的标签。
- 代码必须批量化:永远不要在循环中放置 DML 或 SOQL。始终使用集合来处理批量数据,以避免触及 Governor Limits。
- 尊重安全模型:虽然 Apex 默认在系统模式下运行,但优秀的应用需要考虑用户权限。使用 `with sharing` 和 `Schema` 方法来编写安全的代码。
- 健壮的错误处理:预见并处理 DML 失败的可能。根据业务需求,选择 `try-catch` 或 `Database` 类方法来提供更优雅的用户体验。
- 清晰的命名约定:为自定义对象和字段提供清晰、一致的 API 名称和描述。这不仅有助于自己,也有助于未来维护代码的任何其他开发人员。
通过深入理解自定义对象的原理,并遵循这些开发最佳实践,我们可以构建出高效、稳定且可维护的 Salesforce 解决方案,真正释放平台为客户业务带来的价值。
评论
发表评论