精通 Salesforce 自定义对象:Apex 与 SOQL 开发人员指南
背景与应用场景
在 Salesforce 平台中,Standard Objects (标准对象),如 Account (客户)、Contact (联系人) 和 Opportunity (业务机会),构成了 CRM 功能的核心。然而,几乎没有任何一家企业的业务流程能够完全被这些标准对象所覆盖。每个企业都有其独特的业务需求、数据模型和工作流程。这正是 Custom Objects (自定义对象) 发挥关键作用的地方。
作为一名 Salesforce 开发人员,自定义对象是我们扩展和定制 Salesforce 以满足特定业务需求的基石。它们允许我们创建新的数据表来存储公司特有的信息。与标准对象不同,自定义对象从零开始构建,我们可以完全控制其属性、字段、关系和用户界面。
应用场景示例:
- 教育行业:一所大学可能需要跟踪课程、学生注册和教授信息。我们可以创建 `Course__c` (课程)、`Professor__c` (教授) 以及一个连接学生和课程的 Junction Object (连接对象) `Enrollment__c` (注册)。
- 房地产行业:一家房地产公司需要管理其房产列表、看房预约和潜在买家。`Property__c` (房产) 和 `Showing_Appointment__c` (看房预约) 等自定义对象将是必不可少的。
- 项目管理:任何需要进行项目跟踪的公司都可以创建 `Project__c` (项目) 和 `Project_Task__c` (项目任务) 对象,通过 Master-Detail Relationship (主从关系) 将任务与项目紧密关联。
对于开发人员来说,自定义对象不仅是数据存储的容器,更是我们业务逻辑的载体。Apex Triggers (触发器)、Apex Classes (Apex 类) 以及 Lightning Web Components (LWC) 等代码都会围绕这些自定义对象进行交互,以实现复杂的自动化和定制化用户体验。
原理说明
从开发者的角度理解自定义对象,需要深入其技术构成。当我们通过 Salesforce 的 Setup (设置) 界面创建一个自定义对象时,平台在后台为我们完成了大量工作。
API 名称 (API Name)
每个自定义对象和自定义字段都有一个唯一的 API Name (API 名称)。自定义对象的 API 名称总是以 `__c` 后缀结尾,例如 `Book__c`。同样,其自定义字段的 API 名称也以 `__c` 结尾,例如 `Title__c` 或 `ISBN__c`。这个 `__c` 后缀是区分自定义组件和标准组件的关键标识。在 Apex、SOQL、Visualforce 页面或 LWC 中,我们必须使用 API 名称来引用这些对象和字段。
SObject 类型
在 Apex 中,每个 Salesforce 对象(无论是标准还是自定义)都对应一个 SObject 类型。当我们创建 `Book__c` 这个自定义对象后,Salesforce 会在后台自动生成一个名为 `Book__c` 的 Apex 类。我们可以像使用任何其他 Apex 类一样来实例化它、设置其属性值,并将其用于 Data Manipulation Language (DML) 操作。
例如,`Book__c myBook = new Book__c();` 这行代码就创建了一个 `Book__c` 类型的 SObject 实例在内存中。
关系字段 (Relationship Fields)
自定义对象之间的关系定义了数据模型的结构,这对于开发人员至关重要。主要有两种关系类型:
- Lookup Relationship (查找关系): 这是一种松散耦合的关系。子对象上的查找字段指向父对象。子记录的生命周期独立于父记录,删除父记录不会自动删除子记录(除非特别配置)。在子对象上,查找关系字段存储的是父记录的 18 位 ID。
- Master-Detail Relationship (主从关系): 这是一种紧密耦合的关系。Detail (子) 记录的生命周期完全依赖于 Master (主) 记录。
- 级联删除:删除主记录会自动删除所有关联的子记录。
- 所有权与共享:子记录继承主记录的 `OwnerId`,并且其共享权限由主记录的共享设置决定。子对象上没有独立的共享按钮。
- 汇总字段:可以在主对象上创建 Roll-Up Summary Fields (汇总摘要字段),用于计算子记录的数量、总和、最小值或最大值。
作为开发人员,在设计数据模型时选择正确的关系类型,直接影响到后续的数据完整性、共享模型和代码实现的复杂性。
示例代码
以下所有代码示例均遵循 Salesforce 官方文档中的标准实践。我们将以一个假设的自定义对象 `Book__c` 为例,该对象包含 `Title__c` (Text)、`Author__c` (Text) 和 `Published_Year__c` (Number) 等字段。
1. 使用 Apex 创建自定义对象记录
在 Apex 中创建新的自定义对象记录是一个基本的 DML 操作。我们首先实例化 SObject,然后填充其字段,最后使用 `insert` 语句将其保存到数据库。
// 示例来源:基于 Apex Developer Guide "Working with sObjects" 章节中的 DML 原则
// 1. 创建一个新的 Book__c 对象实例列表
List<Book__c> booksToInsert = new List<Book__c>();
// 2. 实例化第一个 Book__c 对象并设置字段值
Book__c book1 = new Book__c();
book1.Title__c = 'The Three-Body Problem';
book1.Author__c = 'Cixin Liu';
book1.Published_Year__c = 2008;
booksToInsert.add(book1);
// 3. 实例化第二个 Book__c 对象并设置字段值
// 这种方式更简洁,直接在构造函数中初始化
Book__c book2 = new Book__c(
Title__c = 'Dune',
Author__c = 'Frank Herbert',
Published_Year__c = 1965
);
booksToInsert.add(book2);
// 4. 使用 DML insert 语句将记录列表插入数据库
// 始终对列表进行操作,这是批量化 (Bulkification) 的最佳实践
try {
insert booksToInsert;
// 成功插入后,记录的 Id 字段会被自动填充
for (Book__c b : booksToInsert) {
System.debug('Successfully inserted book with Id: ' + b.Id);
}
} catch (DmlException e) {
// 捕获并处理 DML 异常
System.debug('An error occurred during book insertion: ' + e.getMessage());
}
2. 使用 SOQL 查询自定义对象记录
Salesforce Object Query Language (SOQL) 是我们从数据库中检索数据的语言。查询自定义对象与查询标准对象类似,只需使用其 API 名称即可。
// 示例来源:基于 SOQL and SOSL Reference "SELECT Syntax" 章节
// 查询所有在 2000 年之后出版的书籍
// 在查询中明确指定需要返回的字段是一种好的实践
try {
List<Book__c> recentBooks = [
SELECT Id, Title__c, Author__c, Published_Year__c
FROM Book__c
WHERE Published_Year__c > 2000
ORDER BY Published_Year__c DESC
LIMIT 10
];
// 遍历查询结果并输出
if (!recentBooks.isEmpty()) {
for (Book__c book : recentBooks) {
System.debug('Title: ' + book.Title__c + ', Author: ' + book.Author__c);
}
} else {
System.debug('No books found published after the year 2000.');
}
} catch (QueryException e) {
// 捕获并处理查询异常
System.debug('A SOQL query error occurred: ' + e.getMessage());
}
3. 更新和删除自定义对象记录
更新和删除操作同样是基础的 DML。最佳实践是先查询出需要操作的记录,修改后再执行 `update` 或 `delete`。
// 示例来源:基于 Apex Developer Guide "Data Manipulation Language (DML)" 章节
// 目标:找到 'Dune' 这本书并更新其出版年份,然后将其删除
// 首先,查询需要更新的记录
List<Book__c> booksToUpdate = [SELECT Id, Published_Year__c FROM Book__c WHERE Title__c = 'Dune' LIMIT 1];
if (!booksToUpdate.isEmpty()) {
Book__c duneBook = booksToUpdate[0];
// **更新操作**
duneBook.Published_Year__c = 1966; // 假设这是一个修正
try {
update duneBook; // 执行更新
System.debug('Book with Id ' + duneBook.Id + ' has been updated.');
// **删除操作**
// 在实际应用中,更新和删除通常不会在同一个事务中连续执行
// 这里仅为演示 DML 语句
delete duneBook; // 执行删除
System.debug('Book with Id ' + duneBook.Id + ' has been deleted.');
} catch (DmlException e) {
System.debug('An error occurred during update or delete: ' + e.getMessage());
}
} else {
System.debug('Book "Dune" not found.');
}
注意事项
权限 (Permissions)
作为开发人员,我们编写的代码在特定用户的上下文中运行。如果该用户没有对自定义对象的相应权限,代码将执行失败。务必检查:
- Object Permissions (对象权限): 用户的 Profile (简档) 或 Permission Set (权限集) 必须拥有对 `Book__c` 对象的 Create, Read, Update, Delete (CRUD) 权限。
- Field-Level Security (字段级安全 - FLS): 用户必须拥有对 `Title__c`, `Author__c` 等字段的读取或编辑权限。否则,SOQL 查询可能返回不完整的记录,DML 操作也会失败。
- 在 Apex 中,可以使用 `SObjectType.Book__c.isAccessible()` 或 `SObjectField.Book__c.Title__c.isUpdateable()` 等方法来动态检查权限。在 SOQL 中,使用 `WITH SECURITY_ENFORCED` 子句是强制执行 FLS 的现代最佳实践。
API 限制 (API Limits)
Salesforce 是一个多租户环境,为了保证平台稳定性,所有操作都受到 Governor Limits (执行限制) 的约束。与自定义对象交互时,最需要关注的是:
- SOQL 查询限制:单个事务中 SOQL 查询总数不能超过 100 条。
- DML 语句限制:单个事务中 DML 语句(insert, update, delete 等)总数不能超过 150 次。
- DML 行数限制:单个事务中,所有 DML 操作处理的记录总数不能超过 10,000 条。
这就是为什么批量化 (Bulkification) 如此重要。永远不要在循环中执行 SOQL 查询或 DML 语句,这会导致极易超出 Governor Limits。
错误处理 (Error Handling)
DML 操作并非总是成功。网络问题、验证规则失败、权限不足或触发器异常都可能导致操作失败。强大的代码必须包含稳健的错误处理机制。
- 使用 `try-catch` 块:将所有 DML 操作包裹在 `try-catch` 块中,以捕获 `DmlException` 并优雅地处理错误,例如记录日志或向用户显示友好的错误信息。
- 部分成功处理:当批量插入或更新记录时,可能只有部分记录失败。使用 `Database.insert(recordsToInsert, false)` 方法,其中第二个参数 `allOrNone` 设置为 `false`,可以允许部分成功。该方法返回一个 `Database.SaveResult[]` 数组,我们可以遍历它来识别哪些记录成功、哪些失败,并获取详细的错误信息。
总结与最佳实践
自定义对象是 Salesforce 平台强大定制能力的核心。对于开发人员而言,它们是我们构建复杂业务应用程序的画布。掌握如何通过 Apex 和 SOQL 高效、安全地与自定义对象交互,是成为一名优秀 Salesforce 开发人员的必备技能。
以下是与自定义对象相关的开发最佳实践:
- 分析先于构建:在创建新的自定义对象之前,首先评估是否可以通过自定义标准对象来满足需求。过度创建自定义对象会增加系统的复杂性。
- 清晰的命名约定:为自定义对象和字段提供清晰、有意义的标签 (Label) 和 API 名称 (API Name)。一旦 API 名称被代码引用,修改它将非常困难。
- 充分利用描述:始终为自定义对象和字段填写 Description (描述)。这对于未来的你和其他维护代码的开发人员来说是无价的文档。
- 代码必须批量化:始终假设你的代码需要处理成千上万条记录。编写可扩展的、批量化的 Apex Triggers 和方法,以避免触及 Governor Limits。
- 安全第一:始终在代码设计中考虑共享和权限模型。使用 `WITH SECURITY_ENFORCED` 或手动检查 FLS 和对象权限,确保代码不会意外泄露或修改用户无权访问的数据。
- 编写全面的测试:任何与自定义对象交互的 Apex 代码都必须有对应的测试类。测试用例应覆盖各种场景,包括批量操作、边界条件和预期的失败情况,以确保代码的健壮性和可靠性。
遵循这些原则,你将能够充分利用自定义对象的强大功能,构建出高效、可扩展且安全的 Salesforce 应用程序。
评论
发表评论