Lightweight Migration Issues

I am trying to run a lightweight migration in which I am changing the name of a model property from name to title. The database is already populated with few records. Those records must be preserved.

Here is my schema versions:

enum TripsSchemaV1: VersionedSchema {
    
    static var versionIdentifier: String? = "Initial version"
    
    static var models: [any PersistentModel.Type] {
        [Trip.self]
    }
    
    @Model
    class Trip {
        var name: String
        
        init(name: String) {
            self.name = name
        }
    }
}
enum TripsSchemaV2: VersionedSchema {
    
    static var versionIdentifier: String? = "name changed to title"
    
    static var models: [any PersistentModel.Type] {
        [Trip.self]
    }
    
    @Model
    class Trip {
        @Attribute(originalName: "name") var title: String
        
        init(title: String) {
            self.title = title
        }
    }
}

Migration plan:

enum TripsMigrationPlan: SchemaMigrationPlan {
    
    static var schemas: [any VersionedSchema.Type] {
        [TripsSchemaV1.self, TripsSchemaV2.self]
    }
    
    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }
    
    static let migrateV1toV2 = MigrationStage.lightweight(fromVersion: TripsSchemaV1.self, toVersion: TripsSchemaV2.self)
    
}

And finally the usage:

@main
struct TripsApp: App {
    
    let container: ModelContainer
    
    init() {
           do {
               container = try ModelContainer(for: [Trip.self], migrationPlan: TripsMigrationPlan.self, ModelConfiguration(for: [Trip.self]))
           } catch {
               fatalError("Could not initialize the container.")
           }
       }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(container)
        }
    }
}

When I run the app, all my data for the Trips is gone and I get the following message on the output window. 

Unresolved error loading container Error Domain=NSCocoaErrorDomain Code=134504 "Cannot use staged migration with an unknown coordinator model version." UserInfo={NSLocalizedDescription=Cannot use staged migration with an unknown coordinator model version.}

Any ideas?

Replies

This is still happening in Beta 8.

What does this error even mean?

i have the same problem in iOS17. not beta

I never use the lightweight migration because it does not work for me, never.

I always use custom with willMigrate: nil and didMigrate: nil

    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: SchemaV1.self,
        toVersion: SchemaV2.self,
        willMigrate: nil, didMigrate: nil
    )
    
    static let migrateV2toV3 = MigrationStage.custom(
        fromVersion: SchemaV2.self,
        toVersion: SchemaV3.self,
        willMigrate: nil, didMigrate: nil
    )

I'm getting the same error. Did you find any solution?

I have same issue. custom migration not working.

SwiftData performs these light weight changes automatically without need of explicit code. Some of the changes were adding new properties to the model and renaming the properties etc.

One workaround (from a forum post I'll try to find!) is to create an intermediate model and copy over the data in a complex migration:


enum TripsSchemaV1_0_0: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)
    static var models: [any PersistentModel.Type] { [Trip.self] }

    @Model
    class Trip {
        var name: String
        init(name: String) {
            self.name = name
        }
    }
}

enum TripsSchemaV1_1_0: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 1, 0)
    static var models: [any PersistentModel.Type] { [Trip.self] }

    @Model
    class Trip {
        var name: String
        var title: String? // Migrated in schema v2.0
        init(name: String, title: String? = nil) {
            self.name = name
            self.title = title
        }
    }
}

enum TripsSchemaV2_0_0: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)
    static var models: [any PersistentModel.Type] { [Trip.self] }

    @Model
    class Trip {
        var title: String
        init(title: String) {
            self.title = title
        }
    }
}

Then in your MigrationPlan, move the data:

enum TripsSchemaMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [
            TripsSchemaV1_0_0.self,
            TripsSchemaV1_1_0.self,
            TripsSchemaV2_0_0.self,
        ]
    }

    static var stages: [MigrationStage] {
        [
            v1_0_0_to_v0_1_1_lightweight,
            v1_1_0_to_v0_2_0_custom,
        ]
    }

    /// Filling in with `nil`, which SwiftData will happily do :)
    private static let v1_0_0_to_v0_1_1_lightweight = MigrationStage.lightweight(
        fromVersion: TripsSchemaV1_0_0.self,
        toVersion: TripsSchemaV1_1_0.self
    )
    private static let v1_1_0_to_v0_2_0_custom = MigrationStage.custom(
        fromVersion: TripsSchemaV1_1_0.self,
        toVersion: TripsSchemaV2_0_0.self,
        willMigrate: { context in
            let trips = try context.fetch(FetchDescriptor<TripsSchemaV1_1_0.Trip>())
            // Goal: fill-in the new name.
            for trip in trips {
                trip.title = trip.name
            }
            // Assuming autosave is on.
        },
        didMigrate: nil
    )
}