SwiftData ModelContext Pollution with Multiple ModelContainers and Schemas

I have two different VersionedSchema accessed via two different and distinct in-memory ModelContainers. However, both schemas have a model named Item.

LocalSchema.Item and RemoteSchema.Item have slightly different properties.

If I create and save RemoteSchema.Item in one context then I cannot create and save LocalSchema.Item in a different context due to missing origin property.

enum LocalSchema: VersionedSchema {
static var versionIdentifier: Schema.Version = .init(1, 0, 0)
    static var models: [any PersistentModel.Type] = [
        Item.self
    ]
@Model
    class Item {
        @Attribute(.unique)
        var title: String
        var created: Date
        var modified: Date
        
        init(title: String, created: Date, modified: Date) {
            self.title = title
            self.created = created
            self.modified = modified
        }
    }  
}
enum RemoteSchema: VersionedSchema {
    static var versionIdentifier: Schema.Version = .init(1, 0, 0)
    static var models: [any PersistentModel.Type] = [
        Item.self
    ]
    
    @Model
    class Item {
        var title: String
        var created: Date
        var modified: Date
        var origin: String
        
        init(title: String, created: Date, modified: Date, origin: String) {
            self.title = title
            self.created = created
            self.modified = modified
            self.origin = origin
        }
    }
}

In the above example, saving RemoteSchema.Item will cause LocalSchema.Item to fail.

The error message I see is

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0xa120f3750> setValue:forUndefinedKey:]: the entity Item is not key value coding-compliant for the key "origin".'

Test Code

@Test func createLocalItemWithManualSave() async throws {
    let context = ModelContext(try localStore())
    let item = LocalSchema.Item(title: "local", created: .now, modified: .now)
    context.insert(item)
    try context.save()
}

@Test func createRemoteItemWithManualSave() async throws {
    let context = ModelContext(try remoteStore())
    let item = RemoteSchema.Item(title: "remote", created: .now, modified: .now, origin: "from space")
    context.insert(item)
    try context.save()
}

func localStore() throws -> ModelContainer {
    let schema = Schema(versionedSchema: LocalSchema.self)
    let config = ModelConfiguration("local", schema: schema, isStoredInMemoryOnly: true, allowsSave: true, cloudKitDatabase: .none)
    return try ModelContainer(for: schema, configurations: config)
}

func remoteStore() throws -> ModelContainer {
    let schema = Schema(versionedSchema: RemoteSchema.self)
    let config = ModelConfiguration("remote", schema: schema, isStoredInMemoryOnly: true, allowsSave: true, cloudKitDatabase: .none)
    return try ModelContainer(for: schema, configurations: config)
}

I have created FB22310365

Answered by DTS Engineer in 881152022

Thanks for filing the feedback report (FB22310365). It seems to be a known issue that is under the investigation of the SwiftData folks.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Does it crash when you run the app or is it only the tests that crashes?

@joadan yes, the application crashes as well.

The below view will terminated the app if you attempt to save a local item.

struct ContentView: View {
    @State private var localId: Int = 0
    @State private var localCount: Int = 0
    @State private var remoteId: Int = 0
    @State private var remoteCount: Int = 0
    private var localContainer: ModelContainer
    private var remoteContainer: ModelContainer
    private let log = Logger()
    
    init() {
        localContainer = {
            do {
                let schema = Schema(versionedSchema: LocalSchema.self)
                let config = ModelConfiguration("local", schema: schema, isStoredInMemoryOnly: true, allowsSave: true, cloudKitDatabase: .none)
                return try ModelContainer(for: schema, configurations: config)
            }
            catch {
                fatalError("could not create local store")
            }
        }()
        remoteContainer = {
            do {
                let schema = Schema(versionedSchema: RemoteSchema.self)
                let config = ModelConfiguration("remote", schema: schema, isStoredInMemoryOnly: true, allowsSave: true, cloudKitDatabase: .none)
                return try ModelContainer(for: schema, configurations: config)
            }
            catch {
                fatalError("could not create remote store")
            }
        }()
        
    }
    
    var body: some View {
        HStack {
            VStack {
                Text("\(localCount)")
                Button("Save Local") {
                    do {
                        log.info("save local")
                        localId += 1
                        let context = ModelContext(localContainer)
                        let item = LocalSchema.Item(title: "local: \(localId)", created: .now, modified: .now)
                        context.insert(item)
                        try context.save()
                        localCount = try context.fetchCount(FetchDescriptor<LocalSchema.Item>())
                    } catch {
                        log.error("\(error.localizedDescription)")
                    }
                }
            }
            VStack {
                Text("\(remoteCount)")
                Button("Save Remote") {
                    do {
                        log.info("save remote")
                        remoteId += 1
                        let context = ModelContext(remoteContainer)
                        let item = RemoteSchema.Item(title: "remote: \(remoteId)", created: .now, modified: .now, origin: "from space")
                        context.insert(item)
                        try context.save()
                        remoteCount = try context.fetchCount(FetchDescriptor<RemoteSchema.Item>())
                    } catch {
                        log.error("\(error.localizedDescription)")
                    }
                }
            }
        }
    }
}
Accepted Answer

Thanks for filing the feedback report (FB22310365). It seems to be a known issue that is under the investigation of the SwiftData folks.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

OK. Thank you for the reply @DTS Engineer . I'll namespace the conflicting model for now.

Hopefully it won't be too difficult to migrate to the proper fix, if one is provided.

SwiftData ModelContext Pollution with Multiple ModelContainers and Schemas
 
 
Q