【问题标题】:Using vapor-fluent to upsert models使用 vapor-fluent 来更新模型
【发布时间】:2019-06-20 17:45:24
【问题描述】:

我目前正在努力使用蒸汽/流利进行 upsert。我有一个类似这样的模型:

struct DeviceToken: PostgreSQLModel {
    var id: Int?
    var token: String
    var updatedAt: Date = Date()

    init(id: Int? = nil, token: String, updatedAt: Date = Date()) {
        self.id = id
        self.token = token
        self.updatedAt = updatedAt
    }
}

struct Account: PostgreSQLModel {
    var id: Int?
    let username: String
    let service: String
    ...
    let deviceTokenId: DeviceToken.ID

    init(id: Int? = nil, service: String, username: String, ..., deviceTokenId: DeviceToken.ID) {
        self.id = id
        self.username = username
        ....
        self.deviceTokenId = deviceTokenId
    }
}

来自客户端的类似

{
    "deviceToken": {
        "token": "ab123",
        "updatedAt": "01-01-2019 10:10:10"
    },
    "account": {
        "username": "user1",
        "service": "some service"
    }
}

正在发送。

如果新模型不存在,我想做的是插入新模型,否则更新它们。我看到了create(orUpdate:) 方法,但是只有在 id 相同的情况下才会更新(在我的理解中)。由于客户端不发送 id 我不太确定如何处理这个。

我也无法解码模型,因为帐户是在没有deviceTokenId 的情况下发送的,因此解码将失败。我想我可以通过覆盖 NodeCovertible 或使用两种不同的模型(一个用于解码没有 id 的 json 和上面的实际模型)来解决后一个问题。然而,第一个问题仍然存在。

我真正想做的是:

  1. 如果带有令牌的条目已经存在,则更新 DeviceToken,否则创建它

  2. 如果用户名和服务组合的帐户已经存在,则更新其用户名、服务和 deviceTokenId,否则创建它。 DeviceTokenId是1返回的id。

你有没有机会在这里帮助我?

【问题讨论】:

    标签: swift vapor server-side-swift


    【解决方案1】:

    对于所有感兴趣的人: 我通过在 PostgreSQLModel 上编写扩展来提供 upsert 方法来解决它。我添加了一个要点供您查看:here

    由于这些类型的链接有时会在您需要快速概览的信息时断开:

    实际的 upsert 实现:

    extension QueryBuilder
    where Result: PostgreSQLModel, Result.Database == Database {
    
        /// Creates the model or updates it depending on whether a model
        /// with the same ID already exists.
        internal func upsert(_ model: Result,
                             columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {
    
            let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)
    
            /// remove id from row if not available
            /// otherwise the not-null constraint will break
            row = row.filter { (key, value) -> Bool in
                if key == "id" && value.isNull { return false }
                return true
            }
    
            let values = row
                .map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
                    return (.identifier(row.key), row.value)
            }
    
            self.query.upsert = .upsert(columns, values)
            return create(model)
        }
    
    }
    

    便捷方法

    extension PostgreSQLModel {
    
        /// Creates the model or updates it depending on whether a model
        /// with the same ID already exists.
        internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
            return Self
                .query(on: connection)
                .upsert(self, columns: [.keyPath(Self.idKey)])
        }
    
        internal func upsert<U>(on connection: DatabaseConnectable,
                            onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
            return Self
                .query(on: connection)
                .upsert(self, columns: [.keyPath(keyPath)])
        }
    
        ....
    }
    

    我解决了另一个问题,即我的数据库模型无法解码,因为 id 不是从客户端发送的,方法是使用仅包含客户端将发送的属性的内部结构。 id 和其他数据库生成的属性位于外部结构中。类似的东西:

    struct DatabaseModel: PostgreSQLModel {
    
        var id: Int?
        var someProperty: String
    
        init(id: Int? = nil, form: DatabaseModelForm) {
    
            self.id = id
            self.someProperty = form.someProperty
        }
    
        struct DatabaseModelForm: Content {
            let someProperty: String
        }
    }
    

    【讨论】:

    • 知道为什么self.query.upsert = .upsert(columns, values) 编译失败:Cannot infer contextual base in reference to member 'upsert'
    • 这太酷了……你会碰巧有这个的 Fluent 4 版本吗?
    猜你喜欢
    • 1970-01-01
    • 2018-11-27
    • 2017-10-08
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    • 2017-02-10
    • 2021-03-27
    • 2017-08-17
    相关资源
    最近更新 更多