I encountered an error when trying to rollback context after deleting some model with multiple one-to-many relationships when encountered a problem later in a deleting method and before saving the changes. Something like this:
do {
// Fetch model
modelContext.delete(model)
// Do some async work that potentially throws
try modelContext.save()
} catch {
modelContext.rollback()
}
When relationship is empty - the parent has no children - I can safely delete and rollback with no issues. However, when there is even one child when I call even this code:
modelContext.delete(someModel)
modelContext.rollback()
I'm getting a fatal error: SwiftData/ModelSnapshot.swift:46: Fatal error: Unexpected backing data for snapshot creation: SwiftData._FullFutureBackingData<ChildModel>
I use ModelContext from within the ModelActor but using mainContext changes nothing. My ModelContainer is quite simple and problem occurs on both in-memory and persistent storage, with or without CloudKit database being enabled. I can isolate the issue in test environment, so the model that's being deleted (or any other) is not being accessed by any other part of the application. However, problem looks the same in the real app. I also changed the target version of iOS from 18.0 to 26.0, but to no avail.
My models look kind of like this:
@Model
final class ParentModel {
var name: String
@Relationship(deleteRule: .cascade, inverse: \ChildModel.parent)
var children: [ChildModel]?
init(name: String) {
self.name = name
}
}
@Model
final class ChildModel {
var name: String
@Relationship(deleteRule: .nullify)
var parent: ParentModel?
init(name: String) {
self.name = name
}
}
I tried many approaches that didn't help:
- Fetching all children (via
fetch) just to "populate" the context - Accessing all children on parent model (via
let _ = parentModel.children?.count) - Deleting all children reading models from parent:
for child in parentModel.children ?? [] {
modelContext.delete(child)
}
- Deleting all children like this:
let parentPersistentModelID = parentModel.persistentModelID
modelContext.delete(model: ChildModel.self, where: #Predicate { $0.parent.persistentModelID == parentPersistentModelID }, includeSubclasses: true)
- Removing
@Relationship(deleteRule: .nullify)fromChildModelrelationship definition
I found 2 solution for the problem:
- To manually fetch and delete all children prior to deleting parent:
let parentPersistentModelID = parentModel.persistentModelID
for child in try modelContext.fetch(FetchDescriptor<ChildModel>(predicate: #Predicate { $0.parent.persistentModelID == parentPersistentModelID })) {
modelContext.delete(child)
}
modelContext.delete(parentModel)
- Trying to run my code in child context (
let childContext = ModelContext(modelContext.container))
All that sounds to me like a problem deep inside Swift Data itself.
The first solution I found, fetching potentially hundreds of child models just to delete them in case I might need to rollback changes on some error, sounds like awful waste of resources to me.
The second one however seems to work fine has that drawback that I can't fully test my code. Right now I can wrap the context (literally creating class that holds ModelContext and calls its methods) and in tests for throwing methods force them to throw. By creating scratch ModelContext I loose that possibility.
What might be the real issue here? Am I missing something?