【问题标题】:How to create many-to-many association using GRDB in Swift?如何在 Swift 中使用 GRDB 创建多对多关联?
【发布时间】:2021-11-16 15:16:58
【问题描述】:

我正在尝试在歌曲和专辑之间建立关联。每首歌可以出现在一个或多个专辑中,并且每个专辑可以包含一首或多首歌曲。我决定使用GRDB 作为我的数据库解决方案,但我被困在这个问题上。

我尝试了什么: 正如文档所示,我创建了一个 passport 结构,如下所示:

public struct AlbumPassport: TableRecord {
    static let track = belongsTo(SPTTrack.self)
    static let album = belongsTo(SPTAlbum.self)
}

然后在 SPTTrack 类中:

public static let passports = hasMany(AlbumPassport.self)
public static let albums = hasMany(SPTAlbum.self, through: passports, using: AlbumPassport.album)

在 SPTAlbum 类中:

public static let passports = hasMany(AlbumPassport.self)
public static let tracks = hasMany(SPTTrack.self, through: passports, using: AlbumPassport.track)

我在documentation 中找不到关于如何使用这些关联构建请求的好例子。在 SPTAlbum 类中,我添加了 linkedTracks 属性

public var linkedTracks: QueryInterfaceRequest<SPTTrack> {
    request(for: Self.tracks)
}

然后在我的数据库管理器中:

func fetchTracks(for album: SPTAlbum) -> [SPTTrack] {
    do {
        return try dbQueue.read { db in
            try album.linkedTracks.fetchAll(db)
        }
    } catch {
        print(error)
    }
    return []
}

我遇到了错误:

SQLite 错误 1:没有这样的表:albumPassport

这是不言自明的,但我不知道我应该如何以及在哪里为 AlbumPassport 结构创建表,如果有任何其他步骤我应该采取实际使用专辑/曲目连接填充此表。

SPTTrack/SPTAlbum 都有一个名为 id 的字段,在第一次迁移期间设置为 primaryKey

【问题讨论】:

    标签: ios swift sqlite grdb


    【解决方案1】:

    您的关联没有问题。所有hasMany()belongsTo() 都是正确的。您收到的错误告诉我您的数据库设置有问题(您的问题中没有包含)。

    这是我将如何实现它:

    import GRDB
    
    struct Album: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
        mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
        var id: Int64?
        var name: String
        static let passports = hasMany(AlbumPassport.self)
        static let tracks = hasMany(Track.self, through: passports, using: AlbumPassport.track)
    }
    
    struct Track: Codable, Hashable, FetchableRecord, MutablePersistableRecord {
        mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID }
        var id: Int64?
        var name: String
        static let passports = hasMany(AlbumPassport.self)
        static let albums = hasMany(Album.self, through: passports, using: AlbumPassport.album)
    }
    
    struct AlbumPassport: Codable, Hashable, FetchableRecord, PersistableRecord {
        let track: Int64
        let album: Int64
        static let track = belongsTo(Track.self)
        static let album = belongsTo(Album.self)
    }
    
    let queue = DatabaseQueue()
    try queue.write { db in
        
        try db.create(table: "album") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
        }
        try db.create(table: "track") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("name", .text).notNull()
        }
        try db.create(table: "albumPassport") { t in
            t.column("track", .integer).notNull().indexed().references("track")
            t.column("album", .integer).notNull().indexed().references("album")
            t.primaryKey(["track", "album"])
        }
        
        // Testing real data from https://music.apple.com/de/artist/yiruma/73406786
        
        var solo = Album(name: "SOLO")
        try solo.insert(db)
        var sometimes = Track(name: "Sometimes Someone")
        try sometimes.insert(db)
        try AlbumPassport(track: sometimes.id!, album: solo.id!).insert(db)
        var destiny = Track(name: "Destiny Of Love")
        try destiny.insert(db)
        try AlbumPassport(track: destiny.id!, album: solo.id!).insert(db)
        
        var bestOf = Album(name: "Best of Yiroma")
        try bestOf.insert(db)
        var poem = Track(name: "Poem")
        try poem.insert(db)
        try AlbumPassport(track: poem.id!, album: bestOf.id!).insert(db)
        var river = Track(name: "River Flows In You")
        try river.insert(db)
        try AlbumPassport(track: river.id!, album: bestOf.id!).insert(db)
    }
    
    // Fetch all albums and their tracks
    try queue.read { db in
        struct AlbumInfo: FetchableRecord, Decodable, CustomStringConvertible {
            var album: Album
            var tracks: Set<Track>
            var description: String { "\(album.name) → \(tracks.map(\.name))" }
        }
        let request = Album.including(all: Album.tracks)
        let result = try AlbumInfo.fetchAll(db, request)
        print(result)
        // > [SOLO → ["Sometimes Someone", "Destiny Of Love"], Best of Yiroma → ["River Flows In You", "Poem"]]
    }
    

    参见Many-to-many relationship where one entity has multiple properties of the same type 中的my answer 以获取另一个代码示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多