Es kommt häufig vor, dass man eine große Anzahl von Datensätzen in einem Rutsch ändern muss. Dafür bietet Core Data den NSBatchUpdateRequest an. Im nachfolgenden Beispiel wollen wir das Attribut visible bei allen Datensätzen, bei denen es false ist, auf true setzen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
let batchRequest = NSBatchUpdateRequest(entityName: "Entity") batchRequest.predicate = NSPredicate(format: "visible == %@", false) batchRequest.propertiesToUpdate = ["visible" : true] batchRequest.resultType = .UpdatedObjectsCountResultType var error: NSError? = nil if let request = managedObjectContext.executeRequest(batchRequest, error: &error) as? NSBatchUpdateResult { if let numberOfAffectedRows = request.result as? Int { println("\(numberOfAffectedRows) Datensätze geändert") } } |
In dem Beispiel lassen wir uns über den ResultType UpdatedObjectsCountResultType auch noch die Anzahl der geänderten Datensätze zurückgeben.
Und was ist mit dem Cache?
Ein Problem gibt es, wenn wir in anderen Objekten auf die gerade geänderten Daten zugreifen oder nach diesen filtern wollen. Core Data speichert nämlich alle NSManagedObject in einem Cache und dort sind auch nach dem Update noch die alten Daten enthalten, bei denen visible gleich false ist. Um das zu umgehen, müssen wir die betroffenen Objekte neu laden und die gerade gemachten Änderungen berücksichtigen.
Der einfachste Weg dafür ist die eindeutige ObjectID, mit der Core Data jedes Objekt identifizieren kann. Praktischerweise können wir als resultType gleich .UpdatedObjectIDsResultType angeben und erhalten so als Ergebnis nicht die Anzahl der geänderten Datensätze, sondern eine Liste alle ObjectIDs. Dann müssen wir eigentlich nur noch die betroffenen Objekte holen und mit refreshObject die Daten aktualisieren. Unser erweitertes Beispiel sieht dann so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
let batchRequest = NSBatchUpdateRequest(entityName: "Entity") batchRequest.predicate = NSPredicate(format: "visible == %@", false) batchRequest.propertiesToUpdate = ["visible" : true] batchRequest.resultType = .UpdatedObjectIDsResultType var error: NSError? = nil if let request = managedObjectContext.executeRequest(batchRequest, error: &error) as? NSBatchUpdateResult { if let resultObjectIDs = request.result as? [NSManagedObjectID] { for objectID in resultObjectIDs { let managedObject = managedObjectContext.objectWithID(objectID) if !managedObject.fault { managedObjectContext.refreshObject(managedObject, mergeChanges: true) } } } } |
Als Ergebnis bekommen wir ein Array mit NSManagedObjectID und holen uns von Managed Object Context das entsprechende NSManagedObject für die ObjectID. Weist das Objekt keinen Fehler auf, laden wir das Objekt mit refreshObject neu und legen über mergeChanges:true fest, dass die gerade gemachten Änderungen kombiniert werden sollen.
Danach haben alle betroffenen Objekte aktuelle Daten und auch ein Filter über die Objekte funktioniert.