You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

关键修复点

  1. 表名映射:给v1和v2模型添加@Model(originalName: "UnitSize"),确保SwiftData能识别旧数据库中的表,避免因类名变更导致的Schema不匹配。
  2. 迁移逻辑修正:在自定义迁移中,需要先获取v1的实例,转换为v2实例后插入上下文,而不是直接查询v2实例(此时v2实例还未存在)。
  3. 简化容器配置:使用迁移计划中的Schema,无需手动重复构建,减少配置错误。
  4. 首次迁移测试:测试前删除旧应用数据,确保从干净的v1版本开始迁移,避免未知Schema版本干扰。

内容的提问来源于stack exchange,提问作者Doug Idle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:12:02