SwiftData轻量迁移添加带默认值非可选关联属性崩溃求助
SwiftData轻量迁移中添加非可选关联属性的问题与解决方案
问题原因解析
你遇到的错误entity=Car, attribute=engine, reason=Validation error missing attribute values on mandatory destination relationship,核心原因是SwiftData轻量迁移无法自动为自定义关联类型的非可选属性生成默认实例。
轻量迁移仅能处理有限的场景,包括:
- 添加任意类型的可选属性(原始类型或关联类型)
- 添加带默认值的原始类型非可选属性(比如
var age: Int = 18) - 修改属性名称(配合
@Attribute(.originalName("oldName"))) - 添加/移除模型实体等
但对于自定义关联类型(比如你的Engine、示例中的Company这类模型对象)的非可选属性,轻量迁移没办法自动创建对应的实例并完成关联,因此会触发必填属性缺失的验证错误。这种情况下必须通过自定义迁移来实现。
自定义迁移实现示例(给User添加带默认值的Company关联属性)
初始版本模型(V1)
@Model final class User { var name: String init(name: String) { self.name = name } }
更新版本模型(V2)
新增非可选的Company关联属性,默认值为Company(name: "Default Corp"):
@Model final class User { var name: String var company: Company // 非可选关联属性 init(name: String, company: Company = Company(name: "Default Corp")) { self.name = name self.company = company } } @Model final class Company { var name: String init(name: String) { self.name = name } }
步骤1:配置模型容器与迁移计划
修改PersistenceController,启用自定义迁移并指定迁移计划:
struct PersistenceController { static let shared = PersistenceController() let container: ModelContainer init() { let schema = Schema([ User.self, Company.self ]) let modelConfig = ModelConfiguration( schema: schema, migrationPlan: MigrationPlan.self ) do { container = try ModelContainer(for: schema, configurations: [modelConfig]) } catch { fatalError("初始化ModelContainer失败: \(error)") } } }
步骤2:定义迁移计划与版本Schema
创建迁移计划,实现从V1到V2的自定义迁移逻辑:
// 迁移计划 enum MigrationPlan: SchemaMigrationPlan { static var schemas: [VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] } static var stages: [MigrationStage] { [migrateV1ToV2] } // V1到V2的自定义迁移步骤 static let migrateV1ToV2 = MigrationStage.custom( fromVersion: SchemaV1.self, toVersion: SchemaV2.self, willMigrate: { _ in // 迁移前准备操作(可选) }, didMigrate: { context in // 核心逻辑:为所有旧User实例创建默认Company并关联 do { let users = try context.fetch(FetchDescriptor<User>()) for user in users { let defaultCompany = Company(name: "Default Corp") context.insert(defaultCompany) user.company = defaultCompany } try context.save() } catch { print("迁移出错: \(error)") } } ) } // V1版本Schema enum SchemaV1: VersionedSchema { static var version: Schema.Version = .init(1, 0, 0) static var entities: [any PersistentEntity.Type] { [UserV1.self] } @Model final class UserV1 { var name: String init(name: String) { self.name = name } } } // V2版本Schema enum SchemaV2: VersionedSchema { static var version: Schema.Version = .init(2, 0, 0) static var entities: [any PersistentEntity.Type] { [UserV2.self, CompanyV2.self] } @Model final class UserV2 { var name: String var company: CompanyV2 init(name: String, company: CompanyV2 = CompanyV2(name: "Default Corp")) { self.name = name self.company = company } } @Model final class CompanyV2 { var name: String init(name: String) { self.name = name } } }
关键注意事项
- 自定义迁移中,旧版本模型(如
UserV1)和新版本模型(如UserV2)的同名属性会被自动映射,无需额外配置。 - 必须在
didMigrate闭包中手动创建关联模型实例、插入上下文并完成关联,最后保存上下文。 - 后续版本迭代时,只需在
MigrationPlan中新增对应的MigrationStage和VersionedSchema即可。
内容的提问来源于stack exchange,提问作者bauerMusic
相关产品推荐
相关产品推荐

