Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data Entity Unique Constraint Does Not Work

I am trying to set a constraint in core data with the new Entity Constraints inspector (To make the name of the item unique). All that I've read says it's pretty simple - Set the constraint and handle the error. I don't get any errors and can add the same entry as many times as I want.

The app does require IOS 9.0, Xcode tools requirement is set to 7.0

The constraint, category1Name, is a String.

My addItem code is:

func addNewRecord() {

    //check to be sure the entry is not empty
    if (categoryTextField.text == "") {

        //prompt requiring a name
        let ac = UIAlertController(title: nil, message: "Name Required", preferredStyle: .Alert)
        ac.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
        self.presentViewController(ac, animated: true, completion: nil)

    } else {

    let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Category1", inManagedObjectContext: kAppDelegate.managedObjectContext) as! Category1

    newManagedObject.category1Name = categoryTextField.text
    newManagedObject.category1Description = categoryTextView.text

    //bunch more items...

    //save it
    kAppDelegate.saveContext()
    makeEntryFieldsEnabledNO()
    performSegueWithIdentifier("unwindToCategoriesTableViewController", sender: self)

    }//if  else

}//addNewRecord

The AppDelegate save is standard:

func saveContext () {
    if managedObjectContext.hasChanges {
        do {
            try managedObjectContext.save()
        } catch {

            //insert your standard error alert stuff here
            let nserror = error as NSError
            print("From the print line: Unresolved error \(nserror), \(nserror.userInfo)")

            abort()
        }//do catch
    }//if moc
}//saveContext

Here's the Core Data constraint:

enter image description here

This app is iCloud enabled.

The managedObjectContext merge policy is set to NSMergeByPropertyObjectTrumpMergePolicy

lazy var managedObjectContext: NSManagedObjectContext = {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    return managedObjectContext

}()//var managedObjectContext

Any guidance would be appreciated.

like image 280
JohnSF Avatar asked Feb 06 '23 23:02

JohnSF


1 Answers

It would appear Apple have finally fixed the crazy Xcode problem where changes you make in a data model file don't actually change.

Putting that aside, the current formula seems to be:

in your core data singleton ...

    container = NSPersistentContainer(name: _nom)
    
    // during development, right HERE likely delete the sql database file
    // and start fresh, as described here stackoverflow.com/a/60040554/294884
    
    container.loadPersistentStores { storeDescription, error in
        if let error = error {
            print("\n ERROR LOADING STORES! \(error) \n")
        }
        else {
            print("\n  STORES LOADED! \(storeDescription) \n")
        }
    
        self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        self.container.viewContext.automaticallyMergesChangesFromParent = true
    }

You must use merge policy and automatically merges.

Then in your data model file

  1. Don't bother unless every relationship has an inverse, with
  2. "to one or many" correctly set
  3. and (almost certainly, except in very unusual source data) your unique id for each entity is indicated as a constraint

Then when you add new data, you must

  1. use the new background context supplied by the handy core data function which does that
  2. so, never try to make your own separate thread
  3. double-check you have done (1) and (2) !
  4. when you do add a few entities, you must do that inside a perform
  5. and when you have finished adding entities (ie on the new thread) you must while still in the perform ...
  6. do a performAndWait which does two things
  7. save the new items (on the new child thread), and then
  8. save the new items (on the main view thread)
  9. naturally for both 7 and 8, you have to check .hasChanges before saving

Easy right?

So something like

let pm = core.container.newBackgroundContext()
pm.perform {
    for onePerson in someNewData {
        ... create your new CDPerson entity ...
    }
    pm.bake()
}

Note that the bake routine is within the perform block,

and it looks like this:

func bake() {
    self.performAndWait {
        if self.hasChanges {
            do {
                try self.save()
            }
            catch {
                print("bake disaster type 1 \(error)")
            }
        }
        
        // OPTIONALLY, SEE BELOW
        if core.container.viewContext.hasChanges {
            do {
                try core.container.viewContext.save()
            }
            catch {
                print("bake disaster type 2 \(error)")
            }
        }
        // OPTIONALLY, SEE BELOW
    }
}

To be clear, notice the pm.bake ... in the function bake(), the self in the first half is indeed that newBackgroundContext which is created for the loop inside the perform.

Note that these days you don't even need to save to the main context

Nowadays automaticallyMergesChangesFromParent seems to work perfectly, if you "do everything in the long list above".

• In the bake above, add a couple print lines to see what is saved to the viewContext. You'll see that nothing, at all, is ever saved. It's all done properly by the child/whatever relationships in the engine

• So in fact, in reality you can just omit that passage of the code. All you have to do is

func bake() {
    self.performAndWait {
        if self.hasChanges {
            do {
                try self.save()
            }
            catch {
                print("bake disaster type 1 \(error)")
            }
        }
}
like image 197
Fattie Avatar answered Feb 13 '23 07:02

Fattie