假设有 Recipe 模型定义如下:
final class Recipe: Model, Content, @unchecked Sendable {
static let schema = "recipes" // 表名
enum FieldKeys {
static let title: FieldKey = "title" // 菜谱名称
static let coverImageId: FieldKey = "coverImageId" // 菜谱封面图ID
static let description: FieldKey = "description" // 菜谱简介
static let difficulty: FieldKey = "difficulty" // 难度级别 (easy / medium / hard)
static let durationMinutes: FieldKey = "duration_minutes" // 准备和烹饪所需的时间 (分钟)
static let servings: FieldKey = "servings" // 适合人数
static let content: FieldKey = "content" // 菜谱步骤,富文本或 JSON 格式
static let createdAt: FieldKey = "created_at" // 创建时间
static let updatedAt: FieldKey = "updated_at" // 更新时间
}
@ID(key: .id)
var id: UUID?
@Field(key: FieldKeys.title)
var title: String
@OptionalParent(key: FieldKeys.coverImageId)
var coverImage: UploadedFile?
@Field(key: FieldKeys.description)
var description: String
@Field(key: FieldKeys.difficulty)
var difficulty: String
@Field(key: FieldKeys.durationMinutes)
var durationMinutes: Int
@Field(key: FieldKeys.servings)
var servings: Int
@Field(key: FieldKeys.content)
var content: String?
@Timestamp(key: FieldKeys.createdAt, on: .create)
var createdAt: Date?
@Timestamp(key: FieldKeys.updatedAt, on: .update)
var updatedAt: Date?
@Children(for: \.$recipe)
var ingredients: [RecipeIngredient] // 食材
@Siblings(through: RecipeTag.self, from: \.$recipe, to: \.$tag)
var tags: [Tag] // 标签
init() {}
init(id: UUID? = nil, title: String, coverImageId: UUID? = nil, description: String) {
self.id = id
self.title = title
self.$coverImage.id = coverImageId
self.description = description
self.difficulty = "easy" // 默认设置为 "easy"
self.durationMinutes = 0 // 默认时长为 0 分钟
self.servings = 1 // 默认适合人数为 1
}
}
要知道一个Recipe 实例的 tags 有没有已经被取值(加载),你可以检查 Fluent 提供的 $tags 属性的状态,但 Fluent 本身并没有提供一个明显的 isLoaded API。
可以用以下可靠的方式来判断:
✅ 方式 1:尝试读取 $tags.value
if let tags = recipe.$tags.value {
print("✅ 已加载 tags: \(tags)")
} else {
print("❌ tags 还未加载,需要 .get(on:) 或 .with()")
}
recipe.$tags.value为nil表示还没有加载(lazy 状态)- 有值说明已经通过
.with()或.load()加载了
🔍 示例代码
let recipe = try await Recipe.query(on: req.db)
.filter(\.$id == recipeID)
.first()
if let recipe = recipe {
if let tags = recipe.$tags.value {
print("✅ 已经加载了 tags: \(tags.map { $0.name })")
} else {
print("❌ 尚未加载 tags,手动加载中...")
let tags = try await recipe.$tags.get(on: req.db)
print("加载成功: \(tags.map { $0.name })")
}
}
🔍 为什么默认不会加载关联数据?
Fluent 遵循 延迟加载(Lazy Loading) 的原则:
-
@Siblings、@Children、@Parent这些关系字段本身只是一个“查询通道”; -
你必须显式调用
.load(on:)或.load(on:)来发起查询; -
默认只加载模型本身的数据,避免自动 JOIN 导致性能问题。
✅ 延伸:如何确保总是加载好?
用 .with(\.$tags) 提前加载是最佳做法,之后你就能直接访问 recipe.tags 而不担心是空:
let recipe = try await Recipe.query(on: req.db)
.with(\.$tags)
.filter(\.$id == recipeID)
.first()
let tags = recipe?.tags
为什么这么简单的特性,我需要在本文中拎出来单独强调,因为在模型间的关联越复杂的时候,某些方法不总是需要加载关联字段的详情,而有些地方需要,甚至本身就是忘却了加载,这个时候,我们的输出模型就需要兼容这两种情况,不然就会造成代码错误。
let mealRecipes = try await meal.$recipes.query(on: req.db)
.sort(\.$sortOrder, .ascending)
.with(\.$recipe, { recipe in
recipe.with(\.$ingredients)
recipe.with(\.$tags)
recipe.with(\.$coverImage)
})
.all()
更多文章,请关注微信公众号:OldBird