modo-studio/SugarRecord

Object Attribute Updates are not Persisted

Jon-Schneider opened this issue · 3 comments

Version: 2.2.9 and 2.3.1, backed with Core Data.

This is likely just my own implementation issue, but there's an unanswered SO question from someone with the same problem posted this morning (http://stackoverflow.com/questions/39391073/sugarrecord-how-to-change-some-property-and-save-it) so I thought I'd get input.

What

Objects can be inserted, but after insertion updates to managed object properties will not be persisted.

Context

My Stack Setup:

db is a global.

let db = try! CoreDataDefaultStorage(store: CoreData.Store.Named("Store"), model: CoreData.ObjectModel.Merged([NSBundle.mainBundle()]))

This Works Correctly

Inserted Objects will be available through fetches after insertion and persist when the app is killed and reopened.

for integer: Int in 1...100 {
        do {
            try db.operation { (context, save) throws -> Void in
                let newObj: MyObj = try! context.new()
                newObj.name = "Name"

                try! context.insert(newObj)
                save()
            }
        }
        catch {
            NSLog("Import Error: \(error)")
            // There was an error in the operation
        }
}

This does not work correctly

Changed Attributes will be reflected in fetched objects but are not persisted - they will be gone after the app is killed and then reopened.

try! db.operation { (context, save) throws -> Void in
        for myObj in try! db.fetch(Request<MyObj>().sortedWith("name", ascending: true)) {
            myObj.name = "Newer Name"
        }
        save()
}

Hey @Jon-Schneider . I looked at your code and you're not using the proper context for fetching the items in the operation. When you call operation you get two parameters, the context and th save method. The context is the context where you should execute the database queries. In your case you're using the database instead (which in private uses the main context). When you save, you're actually saving the background context (and you didn't modify anything there). Try with this:

try! db.operation { (context, save) throws -> Void in
        for myObj in try! context.fetch(Request<MyObj>().sortedWith("name", ascending: true)) {
            myObj.name = "Newer Name"
        }
        save()
}

The array fetch above was just for contriving the example. So if I have an object reference in an already-fetched array ([MyObj]) and I need to update an attribute I would need to refetch it via the operation block context? Currently, something like the following won't work:

class MyObj: NSManagedObject {
  func updateAttribute(updatedValue: String) {
        do {
            try db.operation { (context, save) throws -> Void in
                self.updatedAttribute = updatedValue
                save()
            }
        }
        catch {
            NSLog("Error: \(error)")
        }
    }
}

Hey @Jon-Schneider . I understand the problem you're trying to solve but due to how CoreData works I'd change the approach.

  • When objects are fetched from CoreData they belong to a context. Its properties are dynamically fetched.
  • If you change any of these properties they are not persisted. They are just reflected in the context as changes to be persisted. Once you save the context, these changes are persisted.
  • Due to that, it's not a good practice to mutate the state of your fetched NSManagedObjects and trying to persist the change afterward. What should you do then?
    • When you fetch something, map it into an immutable type decoupled from core data.
    • When you need to update the model in the database, perform an operation, fetch the object, change what you need to change, and save the context.
    • If you're interested in reflecting the changes afterward in the UI, you can observe the database, and you'll get the changes for free.

I hope that helps.