SwiftData首次迁移报错:无法使用阶段式迁移(未知协调器模型版本)
SwiftData首次迁移报错解决(新增属性场景)
首次给SwiftData模型类新增属性时执行迁移,遇到错误:
NSLocalizedDescription : Cannot use staged migration with an unknown coordinator model version.
不想将所有模型代码合并到Schema版本文件中,以下是具体代码和解决方案:
v1 模型代码
import Foundation import SwiftData @Model(originalName: "UnitSize") // 新增:指定原表名,关联旧数据库 final class UnitSize_v1 { var unitName: String @Attribute(originalName: "size") var unitSize: Int var cost: Int var gameSystem: String init(unitName: String, unitSize: Int, cost: Int, gameSystem: String) { self.unitName = unitName self.unitSize = unitSize self.cost = cost self.gameSystem = gameSystem } static let sampleData = [ UnitSize_v1(unitName: "Allarus Custodians", unitSize: 2, cost: 130, gameSystem: "40k"), UnitSize_v1(unitName: "Allarus Custodians", unitSize: 3, cost: 195, gameSystem: "40k"), UnitSize_v1(unitName: "Allarus Custodians", unitSize: 5, cost: 325, gameSystem: "40k"), UnitSize_v1(unitName: "Allarus Custodians", unitSize: 6, cost: 390, gameSystem: "40k"), UnitSize_v1(unitName: "Custodian Guard", unitSize: 4, cost: 180, gameSystem: "40k"), UnitSize_v1(unitName: "Custodian Guard", unitSize: 5, cost: 225, gameSystem: "40k"), UnitSize_v1(unitName: "Anathema Psykana Rhino", unitSize: 4, cost: 390, gameSystem: "40k"), UnitSize_v1(unitName: "Shield-Captain", unitSize: 1, cost: 140, gameSystem: "40k"), ] }
v2 模型代码
import Foundation import SwiftData @Model(originalName: "UnitSize") // 保持表名一致,确保迁移是同表结构变更 final class UnitSize_v2 { var unitName: String @Attribute(originalName: "size") var unitSize: Int var cost: Int var allyCost: Int var gameSystem: String init(unitName: String, unitSize: Int, cost: Int, allyCost: Int, gameSystem: String) { self.unitName = unitName self.unitSize = unitSize self.cost = cost self.allyCost = allyCost self.gameSystem = gameSystem } static let sampleData = [ UnitSize_v2(unitName: "Allarus Custodians", unitSize: 2, cost: 130, allyCost: 130, gameSystem: "40k"), UnitSize_v2(unitName: "Allarus Custodians", unitSize: 3, cost: 195, allyCost: 195, gameSystem: "40k"), UnitSize_v2(unitName: "Allarus Custodians", unitSize: 5, cost: 325, allyCost: 325, gameSystem: "40k"), UnitSize_v2(unitName: "Allarus Custodians", unitSize: 6, cost: 390, allyCost: 390, gameSystem: "40k"), UnitSize_v2(unitName: "Custodian Guard", unitSize: 4, cost: 180, allyCost: 180, gameSystem: "40k"), UnitSize_v2(unitName: "Custodian Guard", unitSize: 5, cost: 225, allyCost: 225, gameSystem: "40k"), UnitSize_v2(unitName: "Anathema Psykana Rhino", unitSize: 4, cost: 390, allyCost: 390, gameSystem: "40k"), UnitSize_v2(unitName: "Shield-Captain", unitSize: 1, cost: 140, allyCost: 140, gameSystem: "40k"), ] }
Schema 版本定义
v1 Schema
import Foundation import SwiftData enum MusterSchemaV1: VersionedSchema { static var models: [any PersistentModel.Type] { [ FactionFile.self, Categories.self, Faction.self, GameSystem.self, Ally.self, Detachment.self, Enhancement.self, ArmyUnit.self, ArmyList.self, UnitSize_v1.self, UnitComposition.self, IncludedUnit.self, Models.self, Loadout.self, BaseLoadout.self, Ability.self, Equipment.self, LoadoutLabel.self, AddModels.self, AltModels.self, Subfaction.self, WeaponAdd.self, WeaponAutoAdd.self, WeaponCondition.self, WeaponCombi.self, WeaponMulti.self, WeaponNotLoaded.self, WeaponRatio.self, WeaponSplit.self, WeaponSwap.self, WeaponUpto.self, FavouriteUnit.self ] } static var versionIdentifier: Schema.Version = .init(1, 0, 0) }
v2 Schema
import Foundation import SwiftData enum MusterSchemaV2: VersionedSchema { static var models: [any PersistentModel.Type] { [ FactionFile.self, Categories.self, Faction.self, GameSystem.self, Ally.self, Detachment.self, Enhancement.self, ArmyUnit.self, ArmyList.self, UnitSize_v2.self, UnitComposition.self, IncludedUnit.self, Models.self, Loadout.self, BaseLoadout.self, Ability.self, Equipment.self, LoadoutLabel.self, AddModels.self, AltModels.self, Subfaction.self, WeaponAdd.self, WeaponAutoAdd.self, WeaponCondition.self, WeaponCombi.self, WeaponMulti.self, WeaponNotLoaded.self, WeaponRatio.self, WeaponSplit.self, WeaponSwap.self, WeaponUpto.self, FavouriteUnit.self ] } static var versionIdentifier: Schema.Version = .init(1, 1, 0) }
修正后的迁移计划与ModelContainer代码
import SwiftUI import SwiftData // MARK: SwiftData Models typealias UnitSize = UnitSize_v2 // MARK: Migration Plan enum MusterMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [ MusterSchemaV1.self, MusterSchemaV2.self ] } static var stages: [MigrationStage] { [ migrateV1toV2 ] } static let migrateV1toV2 = MigrationStage.custom(fromVersion: MusterSchemaV1.self, toVersion: MusterSchemaV2.self, willMigrate: nil) { context in // 正确获取v1数据并转换为v2 guard let v1Items = try? context.fetch(FetchDescriptor<UnitSize_v1>()) else { return } for v1Item in v1Items { let v2Item = UnitSize_v2( unitName: v1Item.unitName, unitSize: v1Item.unitSize, cost: v1Item.cost, allyCost: v1Item.cost, // 可根据需求设置默认值 gameSystem: v1Item.gameSystem ) context.insert(v2Item) context.delete(v1Item) } try? context.save() } } @main struct MusterPoint_SwiftApp: App { var sharedModelContainer: ModelContainer = { do { // 无需手动构建Schema,直接使用迁移计划 return try ModelContainer( migrationPlan: MusterMigrationPlan.self, configurations: [ModelConfiguration(isStoredInMemoryOnly: false)] ) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(sharedModelContainer) } }
关键修复点
- 表名映射:给v1和v2模型添加
@Model(originalName: "UnitSize"),确保SwiftData能识别旧数据库中的表,避免因类名变更导致的Schema不匹配。 - 迁移逻辑修正:在自定义迁移中,需要先获取v1的实例,转换为v2实例后插入上下文,而不是直接查询v2实例(此时v2实例还未存在)。
- 简化容器配置:使用迁移计划中的Schema,无需手动重复构建,减少配置错误。
- 首次迁移测试:测试前删除旧应用数据,确保从干净的v1版本开始迁移,避免未知Schema版本干扰。
内容的提问来源于stack exchange,提问作者Doug Idle
相关产品推荐
相关产品推荐

