NSEntityMigrationPolicy Changing an optional to a non-optional

I'm working on updating an existing app. I have 3 entities in core data that need to be changed in a new dataModel.

The first 2 (changing a string to a double) are working flawlessly in my NSEntityMigrationPolicy.

The last one just changes the entity "name" in the first dataModel (String, optional) to "name" in the revised dataModel (String, not optional).

Because I have the first type of change, I have to use the custom migration policy, so I can't have it convert the optional to non-optional automatically (lightweight data migration).


Steps taken

1. In dataModel2, uncheck optional for "name"

2. In my Event class, change "name" from optional to non-optional

3. In my migration policy add:


let newName = sourceInstance.valueForKey("name") as! String

newEvent.setValue(newName, forKey: "name")


This combination crashes the app (reason=Can't find mapping model for migration)

How can I convert an optional to a String in the migration policy?


(I've done many variations trying to figure this out. Every test I've done starts with deleted the app on my phone, re-downloading the current version from the app store, and then trying to upgrade with new data model. If I just change the 2 string to doubles it works fine, make the changes to make the optional - not optional and I get the error.)

Answered by ShadowDES in 104177022

So it turns out to be an easy fix. Since I was early in the process, I had yet to get rid of the abort() in the persistanceStoreCoordinator. With the change to the model, it triggered this before it went to my customMigrationPolicy.

Is that code the right way of dealing with optional values? I thought you were supposed to do something like


if let newName = sourceInstance.valueForKey("name") as String

newEvent.setValue(newName, forKey: "name")


valueForKey was able to return a null optional value, and that as! was only valid to do if you wanted your code to throw an exception when it encounters a null.

It is probably a better practice to check to make sure the optional isn't nil, as you have posted. In my example, I know that the valueForKey has a String value, so unwrapping it the way I have is an option.

Can you prove that all of the existing entities have non-nil values?


In an earlier version of my previous reply (which got eaten by the forum) I hypothesized that your code could be throwing an exception that CoreData is catching and redirecting to its "Mapping Model Not Found" error message.

Yes. I just did another quick test. I unchecked the Optional checkbox on the "name" entity, went into the migration, and added a print(newName) between my two lines of code. In my current test, there are 2 Events, and both names printed out. (It also is fine with converting the strings to doubles that I have).


It doesn't appear to even get that far. I put a print("Converting") at the beginning of the createDestinationInstancesForSourceInstance method, and it appears to crash before it gets to that if I try to convert an optional to a non-optional.


let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: false]


If I have the InferModelAuto set to true - it will convert the optional to non-optional, but it won't convert a string to a double.

If I have that set to false - it will convert strings to doubles, but crashes if I try to change an optional to a non-optional.

In the list of requirements for using the automatic migration that you're asking for:

An optional attribute becoming non-optional, and defining a default value

It sounds silly, but CoreData won't generate the automatic migration mapping for you to migrate an optional value to a non-optional value without a default value even if all of your entities are non-nil. And that's even if you're going to immediately replace all of those default values with non-default values.


This is the point where I probably should have just asked you to show your migration code to ensure that I understood which migration process you're trying to use. Are you following the instruction in "Use a Migration Manager if Models Cannot Be Found Automatically" or are you trying to do an automatic migration followed by cleanup of the values?

Yeah.. just to make sure I'm giving the correct info.


iOS app.


Going from version 1 to version 2 of the dataModel.


Created dataModel2 (and made default)

Changed dataModel2's two properties (latitude and longitude) into Doubles (they were strings)

Created a MappingModel (Event to Event) with all of the entities.

Set the MappingModel to use EventTransformationPolicy


In app delegate - used the following for options for the persistentStoreCoordinator


let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: false]

class EventTransformationPolicy: NSEntityMigrationPolicy {
  
    func fromStringToDouble (string: String) -> Double {
        return (string as NSString).doubleValue
    }
    override func createDestinationInstancesForSourceInstance(sourceInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager) throws {
        print("Converting Event")
  
        let newEvent = NSEntityDescription.insertNewObjectForEntityForName(mapping.destinationEntityName!, inManagedObjectContext: manager.destinationContext)
  
        newEvent.setValue(fromStringToDouble(sourceInstance.valueForKey("latitude") as! String), forKey: "latitude")
        newEvent.setValue(fromStringToDouble(sourceInstance.valueForKey("longitude") as! String), forKey: "longitude")
  
        let newName = sourceInstance.valueForKey("name") as! String
        print(newName)
        newEvent.setValue(newName, forKey: "name")
  
        manager.associateSourceInstance(sourceInstance, withDestinationInstance: newEvent, forEntityMapping: mapping)
    }
}


All of above works fine. But I would like to do more.


I then go into dataModel2 and uncheck Optional for "name" and give it a default value in the dataModel "New Event". I delete the app from my phone, re-install from the AppStore, create a new event and give it a name. I then quit the old version of the app, run the updated version of the app and get the following error.


CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:/

NSInferMappingModelAutomaticallyOption = 0;

NSMigratePersistentStoresAutomaticallyOption = 1;

} ... returned error Error Domain=NSCocoaErrorDomain Code=134140 "Persistent store migration failed, missing mapping model." UserInfo={destinationModel=(<NSManagedObjectModel: 0x15c577790>) isEditable 0, entities

(If I then make "name" an Optional again, re-run and it works fine)

Accepted Answer

So it turns out to be an easy fix. Since I was early in the process, I had yet to get rid of the abort() in the persistanceStoreCoordinator. With the change to the model, it triggered this before it went to my customMigrationPolicy.

NSEntityMigrationPolicy Changing an optional to a non-optional
 
 
Q