【问题标题】:Vapor 3, Fluent 3 and Many-to-Many relations not working as expectedVapor 3、Fluent 3 和多对多关系未按预期工作
【发布时间】:2018-12-28 23:57:51
【问题描述】:

刚开始使用 Vapor 3 和 MySQL 数据库,我很难弄清楚关系部分。

到目前为止,我已经创建了 2 个模型:MovieActor。 一个Movie 可以有多个Actors,一个Actor 可以有很多Movies。

Movie模特:

import Vapor
import FluentMySQL

final class Movie: Codable {

    var id: Int?
    var name: String
    var synopsis: String
    var dateReleased: Date
    var totalGrossed: Float

    init(id: Int? = nil, name: String, synopsis: String, dateReleased: Date, totalGrossed: Float) {
        self.id = id
        self.name = name
        self.synopsis = synopsis
        self.dateReleased = dateReleased
        self.totalGrossed = totalGrossed
    }

}

extension Movie {
    var actors: Siblings<Movie, Actor, MovieActor> {
        return siblings()
    }
}

extension Movie: Content {}
extension Movie: Parameter {}
extension Movie: MySQLModel {}

extension Movie: MySQLMigration {
    static func prepare(on conn: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.create(self, on: conn) { builder in
            builder.field(for: \.id, isIdentifier: true)
            builder.field(for: \.name)
            builder.field(for: \.synopsis)
            builder.field(for: \.dateReleased, type: .date)
            builder.field(for: \.totalGrossed, type: .float)
        }
    }
}

Actor模特:

import Vapor
import FluentMySQL

final class Actor: Codable {

    var id: Int?
    var firstName: String
    var lastName: String
    var fullName: String {
        return firstName + " " + lastName
    }
    var dateOfBirth: Date
    var story: String

    init(id: Int? = nil, firstName: String, lastName: String, dateOfBirth: Date, story: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
        self.dateOfBirth = dateOfBirth
        self.story = story
    }

}

extension Actor {
    var actors: Siblings<Actor, Movie, MovieActor> {
        return siblings()
    }
}

extension Actor: Content {}
extension Actor: Parameter {}
extension Actor: MySQLModel {}

extension Actor: MySQLMigration {
    static func prepare(on conn: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.create(self, on: conn) { builder in
            builder.field(for: \.id, isIdentifier: true)
            builder.field(for: \.firstName)
            builder.field(for: \.lastName)
            builder.field(for: \.dateOfBirth, type: .date)
            builder.field(for: \.story, type: .text)
        }
    }
}

我还为关系创建了一个MovieActor 模型作为MySQLPivot

import Vapor
import FluentMySQL

final class MovieActor: MySQLPivot {

    typealias Left = Movie
    typealias Right = Actor

    static var leftIDKey: LeftIDKey = \.movieID
    static var rightIDKey: RightIDKey = \.actorID

    var id: Int?

    var movieID: Int
    var actorID: Int

    init(movieID: Int, actorID: Int) {
        self.movieID = movieID
        self.actorID = actorID
    }

}

extension MovieActor: MySQLMigration {}

并将它们添加到 configure.swift 文件中的迁移部分:

var migrations = MigrationConfig()
migrations.add(model: Movie.self, database: .mysql)
migrations.add(model: Actor.self, database: .mysql)
migrations.add(model: MovieActor.self, database: .mysql)
services.register(migrations)

数据库中的所有表都创建得很好,但是在调用get all movies 服务时我没有收到关系。我刚刚收到Movie 的属性:

final class MoviesController {

    func all(request: Request) throws -> Future<[Movie]> {

        return Movie.query(on: request).all()
    }

}

[
    {
        "id": 1,
        "dateReleased": "2017-11-20T00:00:00Z",
        "totalGrossed": 0,
        "name": "Star Wars: The Last Jedi",
        "synopsis": "Someone with a lightsaber kills another person with a lightsaber"
    },
    {
        "id": 3,
        "dateReleased": "1970-07-20T00:00:00Z",
        "totalGrossed": 0,
        "name": "Star Wars: A New Hope",
        "synopsis": "Someone new has been discovered by the force and he will kill the dark side with his awesome lightsaber and talking skills."
    },
    {
        "id": 4,
        "dateReleased": "2005-12-20T00:00:00Z",
        "totalGrossed": 100000000,
        "name": "Star Wars: Revenge of the Sith",
        "synopsis": "Anakin Skywalker being sliced by Obi-Wan Kenobi in an epic dual of fates"
    }
]

您的帮助将不胜感激!非常感谢:)

【问题讨论】:

    标签: swift vapor


    【解决方案1】:

    所以这里的根本问题是Codable 响应中没有提供计算属性。您需要做的是定义一个新类型 MoviesWithActors 并填充它并返回它。或者提供第二个端点,比如/movies/1/actors/,它可以获取特定电影的所有演员。这更适合 REST,但这取决于您的用例,因为您可能不想要额外的请求等

    【讨论】:

      【解决方案2】:

      因此,我相信您希望在查询 Movie 模型时返回的内容中反映这种关系。因此,例如,您希望为电影返回类似的内容:

      {
          "id": 1,
          "dateReleased": "2017-11-20T00:00:00Z",
          "totalGrossed": 0,
          "name": "Star Wars: The Last Jedi",
          "synopsis": "Someone with a lightsaber kills another person with a lightsaber",
          "actors": [
              "id": 1,
              "firstName": "Leonardo",
              "lastName": "DiCaprio",
              "dateOfBirth": "1974-11-11T00:00:00Z",
              "story": "Couldn't get an Oscar until wrestling a bear for the big screen."
          ]
      }
      

      但是,将 Movie 和 Actor 模型作为兄弟模型连接只是让您能够方便地从电影中查询演员,就好像演员是 Movie 模型的属性一样:

      movie.actors.query(on: request).all()
      

      上面那一行返回:Future

      反之亦然,从 Actor 对象访问电影:

      actor.movies.query(on: request).all()
      

      上面那一行返回:Future

      如果您希望它在相同的响应中返回电影及其演员,就像我假设您希望它在上面那样工作,我相信最好的方法是创建这样的内容响应结构:

      struct MovieResponse: Content {
      
          let movie: Movie
          let actors: [Actor]
      }
      

      你的“all”函数现在看起来像这样:

      func all(_ request: Request) throws -> Future<[MovieResponse]> {
      
          return Movie.query(on: request).all().flatMap { movies in
      
              let movieResponseFutures = try movies.map { movie in
                  try movie.actors.query(on: request).all().map { actors in
                      return MovieResponse(movie: movie, actors: actors)
                  }
              }
      
              return movieResponseFutures.flatten(on: request)
          }
      }
      

      此函数查询所有电影,然后遍历每部电影,然后使用“演员”兄弟关系来查询该电影的演员。这个演员查询为它查询演员的每部电影返回一个 Future。映射从该关系返回的内容,以便您可以作为 [Actor] 而不是 Future 访问演员,然后将其与电影组合作为 MovieResponse 返回。

      这个 movieResponseFutures 实际上包含的是一组 MovieResponse 期货: [未来]

      要将这个future 数组转换为一个由一个数组组成的future,您可以使用flatten(on:)。这等待等待每个单独的未来完成,然后将它们全部作为一个未来返回。

      如果您真的想要将 Actor 的数组放在 Movie 对象 json 中,那么您可以像这样构造 MovieResponse 结构:

      struct MovieResponse: Content {
      
          let id: Int?
          let name: String
          let synopsis: String
          let dateReleased: Date
          let totalGrossed: Float
          let actors: [Actor]
      
          init(movie: Movie, actors: [Actor]) {
              self.id = movie.id
              self.name = movie.name
              self.synopsis = movie.synopsis
              self.dateReleased = movie.dateReleased
              self.totalGrossed = movie.totalGrossed
              self.actors = actors
          }
      }
      

      【讨论】:

      • 这是有史以来最酷的答案!太感谢了!只是一个简单的问题,有没有办法在 JSON 响应中返回 actors 对象内的 actors 数组而不是它旁边?
      • @EpicSyntax 我添加了如果你真的想这样做会是什么样子。如果我没有以规范的方式执行此操作,我深表歉意,这实际上是我的第一个 Stack 答案。
      • 按照我最初回答的方式格式化它的一个好处是,可以将 Swift Content/Decodable 结构与可解码模型(电影、演员)一起复制并粘贴到客户端 iOS 应用程序中,并且然后在那一端使用 JSONDecoder。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-03
      • 1970-01-01
      • 2012-09-22
      • 2021-11-27
      • 1970-01-01
      • 1970-01-01
      • 2015-10-26
      相关资源
      最近更新 更多