SwiftData Migration Error: Missing Attribute Values on Mandatory Relationship

I'm currently working on a data model migration in Swift using a custom schema migration plan.

My project involves migrating settings from one schema version to another (SchemaV1 to SchemaV2). Both schema versions include a class named FSViz, but with slightly different properties.

In the newer schema, SchemaV2, I introduced a new property named textSettings of type TextSetting, replacing the textColor property from SchemaV1.

Here's a simplified version of my migration code and class definitions:

Model:

extension SchemaV1 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textColor: TextColor

        init(textColor: TextColor) {
            self.id = UUID()
            self.textColor = textColor
        }
    }
}

extension SchemaV2 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textSettings: TextSetting

        init(textSettings: TextSetting) {
            self.id = UUID()
            self.textSettings = textSettings
        }
    }
}

initMyApp:

public struct InitMyApp {
    static func makeInitialFSViz() -> FSViz? {
        FSViz(textSettings: makeTextSettings())
    }

    public static func makeFSViz() -> FSViz {
        FSViz(textSettings: makeTextSettings())
    }

    static func makeTextSettings() -> TextSetting {
        TextSetting(
                     textColor: TextColor(red: 1, green: 1, blue: 1, alpha: 1)
        )
    }
}

MigrationPlan:

enum MyAppMigrationPlan: SchemaMigrationPlan {
    static var schemas: [VersionedSchema.Type] {
        [SchemaV1.self, SchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }

    static let migrateV1toV2 = MigrationStage.custom(fromVersion: SchemaV1.self, toVersion: SchemaV2.self, willMigrate: nil, didMigrate: { context in
        let fetchDescriptor = FetchDescriptor<SchemaV1.FSViz>()
        let allV1Vizs = try context.fetch(fetchDescriptor)

        for v1Viz in allV1Vizs {
            let newTextSetting = SchemaV2.TextSetting(textColor: v1Viz.textColor)
            let newViz = SchemaV2.FSViz(textSettings: newTextSetting)

           print("migration processing")
            context.insert(newViz)
            try context.save()
        }
    })
}

MyAppContainer:

public typealias FSViz = SchemaV2.FSViz
public typealias TextSetting = SchemaV2.TextSetting

@MainActor
public let MyAppContainer: ModelContainer = {
    do {
        let schema = Schema([])
        let configuration = ModelConfiguration()
        let container = try ModelContainer(for: FSViz.self,
                                           migrationPlan: MyAppMigrationPlan.self)
        let context = container.mainContext
        if try context.fetch(FetchDescriptor<FSViz>()).isEmpty {
            if let fsViz = InitWaveBar.makeInitialFSViz() {
                container.mainContext.insert(fsViz)
            }
            else {
                print("Error: makeInitialFSViz() returned nil")
            }
        }
        return container
    }
    catch {
        fatalError(error.localizedDescription)
    }
}()

However, when running the migration, I encounter the following error:

Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={entity=FSViz, attribute=textSettings, reason=Validation error missing attribute values on mandatory destination relationship}

This error suggests that there are missing attribute values on the mandatory textSettings relationship in the destination schema.

I've double-checked my migration logic to ensure that textSettings is correctly initialized and assigned, but the error persists.

Questions:

How can I resolve the "missing attribute values on mandatory destination relationship" error during the migration?

Is there a specific aspect of handling relationships during migrations in Swift that I'm overlooking?

Any insights or suggestions on how to troubleshoot and resolve this migration issue would be greatly appreciated.

Accepted Reply

The core of the problem was not properly handling the textSettings property during migration.

To solve this, I made the TextSetting (newly added in SchemaV2) in the model to optional.

extension SchemaV1 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textColor: TextColor

        init(textColor: TextColor) {
            self.id = UUID()
            self.textColor = textColor
        }
    }
}
extension SchemaV2 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textSettings: TextSetting?

        init(textSettings: TextSetting) {
            self.id = UUID()
            self.textSettings = textSettings
        }
    }
}

Rather than go into more detail, I hope you might this answer helpful.

  • ** correct - make default value rather then optional.

Add a Comment

Replies

The core of the problem was not properly handling the textSettings property during migration.

To solve this, I made the TextSetting (newly added in SchemaV2) in the model to optional.

extension SchemaV1 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textColor: TextColor

        init(textColor: TextColor) {
            self.id = UUID()
            self.textColor = textColor
        }
    }
}
extension SchemaV2 {
    @Model
    public final class FSViz {
        @Attribute(.unique) public let id: UUID
        public var textSettings: TextSetting?

        init(textSettings: TextSetting) {
            self.id = UUID()
            self.textSettings = textSettings
        }
    }
}

Rather than go into more detail, I hope you might this answer helpful.

  • ** correct - make default value rather then optional.

Add a Comment