iOS Swift/GRDB:无需添加列,如何将Recipes存入独立表
解决方案:拆分JSON解析与GRDB模型,避免数据冗余
核心思路
将JSON解析用的结构体与GRDB数据库模型解耦:解析时完整读取Fruit及其recipes,然后分别将Fruit数据存入Fruit表、recipes数据(关联对应fruitID)存入独立的Recipe表,GRDB的Fruit模型不包含recipes字段,从根源避免冗余。
步骤1:修正JSON解析用的Codable结构
先修复原代码中重复定义Fruit类的问题,确保解析时能正确读取所有字段:
// 用于JSON解析的结构体,包含完整字段 public struct DataResponse: Codable { let fruits: [FruitWithRecipes] enum CodingKeys: String, CodingKey { case fruits } } public struct FruitWithRecipes: Codable { let fruitID: String let name: String let color: String let recipes: [RecipeRaw] enum CodingKeys: String, CodingKey { case fruitID, name, color, recipes } } public struct RecipeRaw: Codable { let title: String let description: String let videoTutorial: String enum CodingKeys: String, CodingKey { case title, description, videoTutorial } }
步骤2:定义GRDB数据库模型
分别创建Fruit和Recipe的GRDB模型,其中Fruit模型不包含recipes字段,Recipe模型添加fruitID作为外键关联:
import GRDB // GRDB的Fruit模型,仅包含需要存入表的字段 struct Fruit: Codable, FetchableRecord, MutablePersistableRecord { let fruitID: String let name: String let color: String enum CodingKeys: String, CodingKey { case fruitID, name, color } // 定义表名 static let databaseTableName = "fruits" // 主键冲突策略 static let persistenceConflictPolicy = PersistenceConflictPolicy( insert: .replace, update: .replace ) } // GRDB的Recipe模型,包含外键fruitID struct Recipe: Codable, FetchableRecord, MutablePersistableRecord { let id: Int64? // 可选自增主键 let fruitID: String let title: String let description: String let videoTutorial: String enum CodingKeys: String, CodingKey { case id, fruitID, title, description, videoTutorial } static let databaseTableName = "recipes" // 定义与Fruit的关联关系 static let fruit = belongsTo(Fruit.self) // 自增主键赋值 mutating func didInsert(with rowID: Int64, for column: String?) { id = rowID } }
步骤3:解析并拆分存储数据
解析JSON后,将FruitWithRecipes拆分为Fruit和Recipe数据,分别存入对应表:
func parseAndStore(jsonData: Data, db: Database) throws { // 解析JSON let decoder = JSONDecoder() let response = try decoder.decode(DataResponse.self, from: jsonData) // 事务内批量存储,保证数据一致性 try db.inTransaction { for fruitWithRecipes in response.fruits { // 存储Fruit数据 let fruit = Fruit( fruitID: fruitWithRecipes.fruitID, name: fruitWithRecipes.name, color: fruitWithRecipes.color ) try fruit.save(db) // 存储关联的Recipe数据 for recipeRaw in fruitWithRecipes.recipes { let recipe = Recipe( id: nil, fruitID: fruitWithRecipes.fruitID, title: recipeRaw.title, description: recipeRaw.description, videoTutorial: recipeRaw.videoTutorial ) try recipe.save(db) } } return .commit } }
补充说明
- 若需要关联查询(比如通过Fruit获取其recipes),可利用GRDB的关联能力:
// 查询指定Fruit的所有recipes func getRecipes(for fruitID: String, db: Database) throws -> [Recipe] { try Recipe.filter(Column("fruitID") == fruitID).fetchAll(db) } - 可通过GRDB迁移工具创建匹配的表结构:
let migrator = DatabaseMigrator() migrator.registerMigration("createFruitsAndRecipes") { db in try db.create(table: "fruits") { t in t.column("fruitID", .text).primaryKey() t.column("name", .text).notNull() t.column("color", .text).notNull() } try db.create(table: "recipes") { t in t.column("id", .integer).primaryKey().autoincrement() t.column("fruitID", .text).notNull().references("fruits", onDelete: .cascade) t.column("title", .text).notNull() t.column("description", .text).notNull() t.column("videoTutorial", .text).notNull() } }
内容的提问来源于stack exchange,提问作者mgrene
相关产品推荐
相关产品推荐

