Random "duplicate column name" crashes using SwiftData

Hello everyone, I am experiencing a very weird issue.

I have a simple relationship between 2 models, that occasionally starts crashing the app, with the following error:

error: <NSPersistentStoreCoordinator: 0x28007f6b0>: Attempting recovery from error encountered during addPersistentStore: 0x282523c60 Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={sourceURL=file:///private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application%20Support/default.store, reason=Cannot migrate store in-place: I/O error for database at /private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application Support/default.store.  SQLite error code:1, 'duplicate column name: Z1POSITIONS', destinationURL=file:///private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application%20Support/default.store, NSUnderlyingError=0x2825c6700 {Error Domain=NSCocoaErrorDomain Code=134110 "An error occurred during persistent store migration." UserInfo={NSSQLiteErrorDomain=1, NSFilePath=/private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application Support/default.store, NSUnderlyingException=I/O error for database at /private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application Support/default.store.  SQLite error code:1, 'duplicate column name: Z1POSITIONS', reason=I/O error for database at /private/var/mobile/Containers/Shared/AppGroup/F8286D67-AC8C-4441-A151-13B5AAA509F3/Library/Application Support/default.store.  SQLite error code:1, 'duplicate column name: Z1POSITIONS'}}}

This is particularly weird because the app runs a few times without any code changes, or any CRUD operations in the database, and then suddenly starts throwing exceptions.

These are my models:

@Model
final class Desk
{
    @Attribute(.unique) let id: UUID
    let name: String?

    @Relationship(deleteRule: .cascade) var positions: [DeskPosition] = []
    
    init(id: UUID, name: String?) {
        self.id = id
        self.name = name
    }
}
@Model
final class DeskPosition {
    let id: UUID
    var title: String
    private var heightInCm: Double
    
    @Transient
    var height: DeskHeight {
        get {
            DeskHeight(value: heightInCm, unit: .centimeters).localized
        }
        set {
            heightInCm = newValue.converted(to: .centimeters).value
        }
    }
    
    init(id: UUID, height: DeskHeight, title: String) {
        self.id = id
        self.heightInCm = height.converted(to: .centimeters).value
        self.title = title
        self.height = height
    }
}

And this is my schema and model container (I tried adding both models to the schema, or just adding the parent model, and does not seem to make a difference):

private var sharedModelContainer: ModelContainer = {
        let schema = Schema([Desk.self, DeskPosition.self])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

I couldn't find anyone else having this same issue. There must be something obviously simple that I am not looking at. Can anyone help? Thank you!

Answered by Ganso in 769460022

I had the exact same problem. On the first startup everything is ok, then on the second one it crashes. It seems SwiftData doesn't see the already existing column for one-to-many relationship. This, in turn, crashes the application because of the duplicated column.

The solution that I found is to explicitly create the inverse relationship in the second model. So, in your case, changing your DeskPosition model to this:

@Model
final class DeskPosition {
    let id: UUID
    var title: String
    var desk: Desk?
    private var heightInCm: Double
       
    @Transient
    var height: DeskHeight {
        get {
            DeskHeight(value: heightInCm, unit: .centimeters).localized
        }
        set {
            heightInCm = newValue.converted(to: .centimeters).value
        }
    }
    
    init(id: UUID, height: DeskHeight, title: String) {
        self.id = id
        self.heightInCm = height.converted(to: .centimeters).value
        self.title = title
        self.height = height
    }
}
Accepted Answer

I had the exact same problem. On the first startup everything is ok, then on the second one it crashes. It seems SwiftData doesn't see the already existing column for one-to-many relationship. This, in turn, crashes the application because of the duplicated column.

The solution that I found is to explicitly create the inverse relationship in the second model. So, in your case, changing your DeskPosition model to this:

@Model
final class DeskPosition {
    let id: UUID
    var title: String
    var desk: Desk?
    private var heightInCm: Double
       
    @Transient
    var height: DeskHeight {
        get {
            DeskHeight(value: heightInCm, unit: .centimeters).localized
        }
        set {
            heightInCm = newValue.converted(to: .centimeters).value
        }
    }
    
    init(id: UUID, height: DeskHeight, title: String) {
        self.id = id
        self.heightInCm = height.converted(to: .centimeters).value
        self.title = title
        self.height = height
    }
}
Random "duplicate column name" crashes using SwiftData
 
 
Q