You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Vapor Fluent实现Profile模型的Upsert操作?

Vapor Fluent 实现 Profile 模型的按需更新与Upsert优化

背景信息

我有一张名为profile的表,对应的Fluent模型代码如下:

final class Profile: Model, Content {
    struct Keys {
        static let userId: FieldKey = "user_id"
        static let nickname: FieldKey = "nickname"
        static let bio: FieldKey = "bio"
    }
    
    static let schema = "profile"
    
    @ID(key: .id) var id: UUID?
    
    @Field(key: Keys.nickname) var nickname: String?
            
    @Field(key: Keys.bio) var bio: String?
    
    @Parent(key: Keys.userId) var user: User
    
    init() {}
    
    init(id: UUID? = nil, nickname: String?, bio: String?, userId: User.IDValue) {
        self.id = id
        self.nickname = nickname
        self.bio = bio
        self.$user.id = userId
    }
}

客户端传来的请求体示例:

{
  "bio": "my new bio"
}

我希望能根据客户端传递的任意属性进行更新,当前的实现代码如下:

let userId = try req.auth.require(User.self).requireID()
guard let oldProfile = try await Profile.query(on: req.db).filter(\.$user.$id == userId).first() else {
    let newProfile = Profile(nickname: edit.nickname, bio: edit.bio, userId: userId)
    try await newProfile.save(on: req.db)
    return GlobalResponse(code: .ok, msg: .custom("save success"))
}

if let b = edit.bio {
    oldProfile.bio = b
}

if let n = edit.nickname {
    oldProfile.nickname = n
}

try await oldProfile.update(on: req.db)
return GlobalResponse(code: .ok, msg: .custom("save success"))

这段代码可以正常运行,但我想知道有没有更优或更简便的实现方式?我还没找到Vapor Fluent中实现Upsert的便捷方法,作为Vapor新手,我尝试查找相关Upsert代码但没有结果。


优化方案

1. 利用Fluent的upsert方法实现原子性插入/更新

Fluent支持基于唯一约束的Upsert操作,首先需要给Profile模型的user_id字段添加唯一约束,确保一个用户只能有一条Profile记录:

先在迁移文件中添加约束(如果还未配置):

struct CreateProfile: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema(Profile.schema)
            .id()
            .field(Profile.Keys.userId, .uuid, .required, .references(User.schema, .id))
            .field(Profile.Keys.nickname, .string)
            .field(Profile.Keys.bio, .string)
            .unique(on: Profile.Keys.userId) // 添加user_id唯一约束
            .create()
    }
    
    func revert(on database: Database) async throws {
        try await database.schema(Profile.schema).delete()
    }
}

之后就可以用upsert方法替代原有逻辑,数据库会原子性处理“存在则更新、不存在则插入”的操作:

let userId = try req.auth.require(User.self).requireID()
let edit = try req.content.decode(Profile.self)

// 整理需要更新的字段
var updateFields: [FieldKey: DatabaseQuery.Value] = [:]
if let nickname = edit.nickname {
    updateFields[Profile.Keys.nickname] = .string(nickname)
}
if let bio = edit.bio {
    updateFields[Profile.Keys.bio] = .string(bio)
}

// 执行Upsert
try await Profile.query(on: req.db)
    .filter(\.$user.$id == userId)
    .upsert(
        update: updateFields,
        create: Profile(nickname: edit.nickname, bio: edit.bio, userId: userId)
    )

return GlobalResponse(code: .ok, msg: .custom("save success"))

2. 简化原有查询更新逻辑(不使用Upsert的情况)

如果暂时不想用Upsert,也可以简化属性更新的判断逻辑:

let userId = try req.auth.require(User.self).requireID()
let edit = try req.content.decode(Profile.self)

if let oldProfile = try await Profile.query(on: req.db).filter(\.$user.$id == userId).first() {
    // 仅覆盖客户端传来的非空属性
    oldProfile.nickname = edit.nickname ?? oldProfile.nickname
    oldProfile.bio = edit.bio ?? oldProfile.bio
    try await oldProfile.update(on: req.db)
} else {
    let newProfile = Profile(nickname: edit.nickname, bio: edit.bio, userId: userId)
    try await newProfile.save(on: req.db)
}

return GlobalResponse(code: .ok, msg: .custom("save success"))

关键说明

  • Upsert的核心依赖唯一约束,必须保证user_id字段唯一,否则无法正确匹配目标记录
  • 使用upsert可以减少一次数据库查询,由数据库层面保证操作的原子性,性能更优
  • 按需更新时,只需将客户端传来的非空字段加入更新字典,数据库只会修改指定字段

内容的提问来源于stack exchange,提问作者yxk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:54:49