从Vapor 3中的连接查询中获取所有字段

时间:2018-02-20 23:51:59

标签: mysql swift vapor

背景

给出以下样本模型(选择2个显示1-n关系的简单示例):

final class Company: MySQLModel {
    var id: Int?
    var name: String
}

final class Client: MySQLModel {
    var id: Int?
    var attr1: Int
    var attr2: String
    var companyId: Company.ID

    static func prepare(on connection: MySQLDatabase.Connection) -> Future<Void> {

        return Database.create(self, on: connection, closure: { builder in
            try addProperties(to: builder)
            builder.addReference(from: \.companyId, to: \Company.id, actions: .update)
        })
    }
}

问题

有没有办法获取&amp;返回JOINED查询的结果(例如:Company - Client // One-to-Many)而不需要原始查询?我尝试使用Query和Relationships但是没有办法一次性获取所有这些。

理想情况下,返回的数据将具有如下嵌套结构:

预期:

{
  "name": "Alice",
  "id": 0000000001,
  "company": {
    "id": 11111111,
    "name": "Stack Overflow"
  }
}

解决方案(又名解决方法)

我确实设法通过使用额外的结构(称为Wrapper,Box,Merged等)来保持所有实体并最终使用makeJSON将其返回到Controller中。

    let query = try db.query(Client.self)
        .filter(\.attr1 > 123)
        .filter(\.attr2 == "abc")
    let client = try query.first()

    // client.company is just an attribute of Client that uses
    // the *parent* method to retrieve it
    if let client = client, let company = try client.company.get() {

        // others uses *children* method
        let others = try client.others.limit(5).all()

        let companyJSON = company.dictionary! // dictionary returns [String:Any] for any Encodable
        let clientJSON = client.dictionary!


        let merged = clientJSON.merging([ "company": companyJSON ], uniquingKeysWith: { (first, _) in first })
        return merged
    }

最终评论

使用包装器实体是唯一的方法(不使用原始查询)?处理多级结果将非常繁琐。

编辑:我已经找到了一个相关问题Is it possible to access fields in a joined table in Vapor?,但答案与我想要的方式不同。

Edit2:我最近迁移到Vapor3,因此新代码。我认为Vapor2的想法是一样的,但是由于Vapor3引入了Codable支持,你必须编写更多的代码。

2 个答案:

答案 0 :(得分:3)

我不确定这是否完全是你想要的,但我想我正在尝试做类似的事情。我想显示一个用户及其相关的auth令牌表。这是以标准方式设置的,在Fluent中设置了父子(一对多)关系。我最终做了以下非常好的工作

func getUsersHandler(_ req: Request) throws -> Future<View> {
    return User.query(on: req).all().flatMap(to: View.self) { users in
        let tokenFutures = try users.map {
            return try $0.authTokens.query(on: req).all()
        }
        return tokenFutures.flatMap(to: View.self) { tokensByUser in
            let usersAndTokens = zip(users, tokensByUser).map {
                return UserAndTokens(user: $0, tokens: $1.map { $0.token})
            }
            let listUsersCtx = ListUsersContext(usersAndTokens: usersAndTokens)
            return try req.leaf().render("users", listUsersCtx)
        }
    }
}

我刚刚学习Vapor,所以我不知道这是不是一个很好的解决方案。通过让用户承诺解决,然后让每个用户 - >令牌承诺解决,一切都保持异步,直到我在可编码的ListUsersContext结构中将所有已解析的东西打包在一起

答案 1 :(得分:2)

我一直在想这个问题,然后昨天我看到它已被添加到文档中:https://docs.vapor.codes/3.0/fluent/querying/#join

所以您应该可以执行类似的操作

Client.query(on: conn).join(\Client.companyId, to: \Company.id)
    .filter(\.attr1 > 12)
    .filter(\Company.name == "ACME")
    .alsoDecode(Company.self)
    .all()

这应该返回一个元组数组(客户,公司)。您应该可以轻松地将这些序列化为所需的JSON,并且不会涉及

我尚未检查同一公司的多个实例是否相等。