SwiftData:迁移时无法创建ModelContainer

问题描述 投票:0回答:1

在创建从 V1 到 V2 的迁移状态后尝试创建 SwiftData ModelContainer 时,我的应用程序在启动时崩溃。

这是我收到的错误:

Thread 1: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)

我唯一的模型从 V1 到 V2 发生了什么变化? 我添加了一个名为

defaultRunTypes: [RunType]
的新属性,其中 RunType 是一个具有 5 种不同情况的枚举。 V1 模型只有
isDefaultShoe: Bool
属性,我想扩展该功能,为不同类型的运行提供多个默认值。

现在,这是一些代码。

这就是我创建容器的方式:

typealias Shoe = ShoesSchemaV2.Shoe

final class ShoesStore {
    
    static let container = {
        let container: ModelContainer
        
        do {
            container = try ModelContainer(for: Shoe.self, migrationPlan: ShoesMigrationPlan.self)
        } catch {
            fatalError("Failed to create ModelContainer for Shoe: \(error.localizedDescription)")
        }
        
        return container
    }()
}

这是迁移计划。

enum ShoesMigrationPlan: SchemaMigrationPlan {
    
    static var schemas: [any VersionedSchema.Type] {
        [ShoesSchemaV1.self, ShoesSchemaV2.self]
    }
    
    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }
    
    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: ShoesSchemaV1.self,
        toVersion: ShoesSchemaV2.self,
        willMigrate: { context in
            print("Migration: willMigrae")
            
            let v1Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV1.Shoe>())
            
            for v1Shoe in v1Shoes ?? [] {
                print("Migration: V1 Shoe: \(v1Shoe.brand) \(v1Shoe.model) - isDefaultShoe = \(v1Shoe.isDefaultShoe)")
                
                let v2Shoe = ShoesSchemaV2.Shoe(
                    id: v1Shoe.id,
                    image: v1Shoe.image,
                    brand: v1Shoe.brand,
                    model: v1Shoe.model,
                    nickname: v1Shoe.nickname,
                    lifespanDistance: v1Shoe.lifespanDistance,
                    aquisitionDate: v1Shoe.aquisitionDate,
                    totalDistance: v1Shoe.totalDistance,
                    totalDuration: v1Shoe.totalDuration,
                    lastActivityDate: v1Shoe.lastActivityDate,
                    isRetired: v1Shoe.isRetired,
                    retireDate: v1Shoe.retireDate,
                    isDefaultShoe: v1Shoe.isDefaultShoe,
                    defaultRunTypes: v1Shoe.isDefaultShoe ? [.daily] : [], // this is the newly added property
                    workouts: v1Shoe.workouts,
                    personalBests: v1Shoe.personalBests,
                    totalRuns: v1Shoe.totalRuns
                )
                
                print("Migration: V2 shoe created: \(v1Shoe.brand) \(v1Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
                
                context.insert(v2Shoe)
                context.delete(v1Shoe)
            }
            
            try? context.save()
        },
        didMigrate: { context in
            print("Migration: didMigrate")
            let v2Shoes = try? context.fetch(FetchDescriptor<ShoesSchemaV2.Shoe>())

            for v2Shoe in v2Shoes ?? [] {
                print("Migration: V2 shoe: \(v2Shoe.brand) \(v2Shoe.model) - defaultRunTypes: \(v2Shoe.defaultRunTypes.count)")
            }
        }
    )
}

迁移时,我有以下控制台输出。

willMigrate
正在执行,但不是
didMigrate

Migration: willMigrate
Migration: V1 Shoe: Adidas Adizero Boston 12 - isDefaultShoe = true
Migration: V2 shoe created: Adidas Adizero Boston 12 - defaultRunTypes: 1
Migration: V1 Shoe: Nike Pegasus Turbo - isDefaultShoe = false
Migration: V2 shoe created: Nike Pegasus Turbo - defaultRunTypes: 0
Migration: V1 Shoe: Nike Zoomfly 5 - isDefaultShoe = false
Migration: V2 shoe created: Nike Zoomfly 5 - defaultRunTypes: 0
Migration: V1 Shoe: Nike Vaporfly 3 - isDefaultShoe = false
Migration: V2 shoe created: Nike Vaporfly 3 - defaultRunTypes: 0
Migration: V1 Shoe: Hoka Speedgoat 6 - isDefaultShoe = false
Migration: V2 shoe created: Hoka Speedgoat 6 - defaultRunTypes: 0

ShoeHealth/ShoesStore.swift:21: Fatal error: Failed to create ModelContainer for Shoe: The operation couldn’t be completed. (SwiftData.SwiftDataError error 1.)

我不知道这里发生了什么,因为这是我正在进行的第一次迁移。我创建 ModelContainer 的方式有问题吗(静态 - 我这样做是因为我也需要在

WidgetKit
中访问它)?

这就是我在 AppIntentTimelineProvider 中访问它的方式:

struct MediumShoeStatsTimelineProvider: AppIntentTimelineProvider {
    
    let modelContext = ModelContext(ShoesStore.container)
    // rest of the code, where I am using the modelContext to fetch models
}

后期编辑: 我尝试从自定义 MigrationState 更改为轻量级,并且它有效。除了事实之外,正如预期的那样,所有鞋子的

defaultRunTypes
都是空的,因为这是默认值。 所以我猜我制作自定义 MigrationState 的方式是错误的。

static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: ShoesSchemaV1.self, toVersion: ShoesSchemaV2.self)
ios swift swiftui swiftdata
1个回答
0
投票

找到解决方案。不确定这是否是正确的方法,但我们开始吧:

static let migrateV1toV2 = MigrationStage.custom(
    fromVersion: ShoesSchemaV1.self,
    toVersion: ShoesSchemaV2.self,
    willMigrate: nil,
    didMigrate: { context in
        let shoes = try context.fetch(FetchDescriptor<ShoesSchemaV2.Shoe>())
        
        for shoe in shoes {
            print("Migration: \(shoe.brand) \(shoe.model)")
            
            shoe.defaultRunTypes = shoe.isDefaultShoe ? [.daily] : []
        }
        
        try context.save()
    }
)

由于我的新属性有默认值,因此我让

willMigrate
执行其操作,然后在
didMigrate
部分中为每只鞋子手动设置新属性值。

说实话,我不知道是什么原因导致了这个问题。也许

willMigrate
不允许在模型上下文中插入,我真的不知道。

我知道

willMigrate
上下文可以让您访问 V1 模型,而
didMigrate
上下文可以让您访问 V2 模型。

注意:不会接受这个答案,因为我正在等待其他解决方案(或者也许这是正确的方法)。

© www.soinside.com 2019 - 2024. All rights reserved.