SwiftData Migrations to Change Property Name

Has anyone able to run SwiftData migrations successfully? I have a Budget class with name property and I want to update it to title property while preserving all the existing data.

I have defined V1 and V2 to Budget class and now I am trying to run a migration plan.

        
        // get all existing budgets
        // This is the V1 Budget. This contains the name property
        let oldBudgets = try? context.fetch(FetchDescriptor<SpendTrackerSchemaV1.Budget>())
        print(oldBudgets?[0].name) // contains the value correctly like Colorado
        
        // SpendTrackerSchemaV2.Budget contains the title property
        
       
        }
        
    }, didMigrate: nil)```

So, oldBudgets contains the budgets with the name property and they have values too. I want to migrate all those Budgets to the Budget with title property. Any ideas or anyone else able to perform migration actions with SwiftData framework. 
  • Did you get any further with this?

    I have the same problem. I need to have access to the source and the destination models to change attribute types and values as part of the migration. How I understand the current MigrationStage logic is that willMigrate gives you access to the source models and didMigrate gives you access to the destination models, but you can't have access to both at the same time, like with the Core Data mapping model and migration policy approach.

    Am I wrong?

Add a Comment

Replies

If you're just renaming a property, you shouldn't need an explicit migration plan and could instead rely on automatic migration, using the originalName parameter of the @Attribute macro:

@Attribute(originalName: "name") let title: String

In terms of how the willMigrate and didMigrate closures work when you do write a custom migration stage, the SwiftData documentation currently isn't helpful, but the equivalent Core Data documentation offers some insight:

The handlers provide an opportunity to prepare the persistent store’s data for the upcoming changes before the stage runs, and perform any cleanup tasks afterward.

For example, to support a migration that changes an optional attribute to be nonoptional, you might assign a handler to the stage’s willMigrateHandler property that sets any nil instances of that attribute to a default value, thereby ensuring the migration succeeds.

The way I understand it is that at its core, a custom stage actually does the same as a lightweight stage, but the two handlers allow you to prepare the data so that the automatic part of the stage can succeed, and to do some clean-up afterwards if you need to.

I guess if you did want to rename a field using a custom stage, you'd have to:

  1. Add an optional title attribute in the V1 model alongside the original name attribute, to allow you to copy the value over.*
  2. In the willMigrate closure, get all the V1 objects and copy their name value into their title field.
  3. The automatic part of the migration stage can now run safely, making title non-optional and removing the name field.

* Note that if your original model wasn't part of an explicit VersionedSchema, you can't introduce the title field in your now-explicit V1 schema because SwiftData won't recognise it as being the same as the implicit 1.0.0 schema it had created. To get around this, I had to create an intermediate schema (1.1.0) with the new field, and added a lightweight migration stage (from 1.0.0 to 1.1.0) to introduce the field before the custom stage (from 1.1.0 to 2.0.0) populated it.